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 "Vehicle.h"
11
12 #include "../Context.h"
13 #include "../Editor.h"
14 #include "../Game.h"
15 #include "../OpenRCT2.h"
16 #include "../actions/RideSetStatusAction.h"
17 #include "../audio/AudioMixer.h"
18 #include "../audio/audio.h"
19 #include "../config/Config.h"
20 #include "../core/Memory.hpp"
21 #include "../interface/Viewport.h"
22 #include "../localisation/Localisation.h"
23 #include "../management/NewsItem.h"
24 #include "../platform/platform.h"
25 #include "../rct12/RCT12.h"
26 #include "../scenario/Scenario.h"
27 #include "../scripting/HookEngine.h"
28 #include "../scripting/ScriptEngine.h"
29 #include "../util/Util.h"
30 #include "../windows/Intent.h"
31 #include "../world/Map.h"
32 #include "../world/MapAnimation.h"
33 #include "../world/Park.h"
34 #include "../world/Particle.h"
35 #include "../world/Scenery.h"
36 #include "../world/SmallScenery.h"
37 #include "../world/Sprite.h"
38 #include "../world/Surface.h"
39 #include "../world/Wall.h"
40 #include "CableLift.h"
41 #include "Ride.h"
42 #include "RideData.h"
43 #include "Station.h"
44 #include "Track.h"
45 #include "TrackData.h"
46 #include "TrainManager.h"
47 #include "VehicleData.h"
48 #include "VehicleSubpositionData.h"
49
50 #include <algorithm>
51 #include <iterator>
52
53 using namespace OpenRCT2::TrackMetaData;
54 static bool vehicle_boat_is_location_accessible(const CoordsXYZ& location);
55
56 constexpr int16_t VEHICLE_MAX_SPIN_SPEED = 1536;
57 constexpr int16_t VEHICLE_MIN_SPIN_SPEED = -VEHICLE_MAX_SPIN_SPEED;
58 constexpr int16_t VEHICLE_MAX_SPIN_SPEED_FOR_STOPPING = 700;
59 constexpr int16_t VEHICLE_MAX_SPIN_SPEED_WATER_RIDE = 512;
60 constexpr int16_t VEHICLE_MIN_SPIN_SPEED_WATER_RIDE = -VEHICLE_MAX_SPIN_SPEED_WATER_RIDE;
61 constexpr int16_t VEHICLE_STOPPING_SPIN_SPEED = 600;
62
63 Vehicle* gCurrentVehicle;
64
65 static uint8_t _vehicleBreakdown;
66 StationIndex _vehicleStationIndex;
67 uint32_t _vehicleMotionTrackFlags;
68 int32_t _vehicleVelocityF64E08;
69 int32_t _vehicleVelocityF64E0C;
70 int32_t _vehicleUnkF64E10;
71 uint8_t _vehicleF64E2C;
72 Vehicle* _vehicleFrontVehicle;
73 CoordsXYZ unk_F64E20;
74
75 static constexpr const OpenRCT2::Audio::SoundId byte_9A3A14[] = {
76 OpenRCT2::Audio::SoundId::Scream8,
77 OpenRCT2::Audio::SoundId::Scream1,
78 };
79 static constexpr const OpenRCT2::Audio::SoundId byte_9A3A16[] = {
80 OpenRCT2::Audio::SoundId::Scream1,
81 OpenRCT2::Audio::SoundId::Scream6,
82 };
83 static constexpr const OpenRCT2::Audio::SoundId byte_9A3A18[] = {
84 OpenRCT2::Audio::SoundId::Scream3, OpenRCT2::Audio::SoundId::Scream1, OpenRCT2::Audio::SoundId::Scream5,
85 OpenRCT2::Audio::SoundId::Scream6, OpenRCT2::Audio::SoundId::Scream7, OpenRCT2::Audio::SoundId::Scream2,
86 OpenRCT2::Audio::SoundId::Scream4,
87 };
88
89 static constexpr const uint8_t _soundParams[OpenRCT2::Audio::RCT2SoundCount][2] = {
90 { 1, 0 }, // LiftClassic
91 { 1, 0 }, // TrackFrictionClassicWood
92 { 1, 0 }, // FrictionClassic
93 { 0, 1 }, // Scream1
94 { 0, 0 }, // Click1
95 { 0, 0 }, // Click2
96 { 0, 0 }, // PlaceItem
97 { 0, 1 }, // Scream2
98 { 0, 1 }, // Scream3
99 { 0, 1 }, // Scream4
100 { 0, 1 }, // Scream5
101 { 0, 1 }, // Scream6
102 { 1, 0 }, // LiftFrictionWheels
103 { 0, 0 }, // Purchase
104 { 0, 0 }, // Crash
105 { 0, 0 }, // LayingOutWater
106 { 0, 0 }, // Water1
107 { 0, 0 }, // Water2
108 { 0, 1 }, // TrainWhistle
109 { 0, 1 }, // TrainDeparting
110 { 0, 0 }, // WaterSplash
111 { 1, 0 }, // GoKartEngine
112 { 0, 0 }, // RideLaunch1
113 { 0, 0 }, // RideLaunch2
114 { 0, 0 }, // Cough1
115 { 0, 0 }, // Cough2
116 { 0, 0 }, // Cough3
117 { 0, 0 }, // Cough4
118 { 1, 0 }, // Rain
119 { 0, 0 }, // Thunder1
120 { 0, 0 }, // Thunder2
121 { 1, 0 }, // TrackFrictionTrain
122 { 1, 0 }, // TrackFrictionWater
123 { 0, 0 }, // BalloonPop
124 { 0, 0 }, // MechanicFix
125 { 0, 1 }, // Scream7
126 { 0, 0 }, // ToiletFlush
127 { 0, 0 }, // Click3
128 { 0, 0 }, // Quack
129 { 0, 0 }, // NewsItem
130 { 0, 0 }, // WindowOpen
131 { 0, 0 }, // Laugh1
132 { 0, 0 }, // Laugh2
133 { 0, 0 }, // Laugh3
134 { 0, 0 }, // Applause
135 { 0, 0 }, // HauntedHouseScare
136 { 0, 0 }, // HauntedHouseScream1
137 { 0, 0 }, // HauntedHouseScream2
138 { 0, 0 }, // BlockBrakeClose
139 { 0, 0 }, // BlockBrakeRelease
140 { 0, 0 }, // Error
141 { 0, 0 }, // BrakeRelease
142 { 1, 0 }, // LiftArrow
143 { 1, 0 }, // LiftWood
144 { 1, 0 }, // TrackFrictionWood
145 { 1, 0 }, // LiftWildMouse
146 { 1, 0 }, // LiftBM
147 { 1, 2 }, // TrackFrictionBM
148 { 0, 1 }, // Scream8
149 { 0, 1 }, // Tram
150 { 0, 0 }, // DoorOpen
151 { 0, 0 }, // DoorClose
152 { 0, 0 }, // Portcullis
153 };
154
155 // clang-format off
156 static constexpr const uint8_t SpaceRingsTimeToSpriteMap[] =
157 {
158 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
159 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4,
160 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8,
161 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13,
162 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18,
163 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 0,
164 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5,
165 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
166 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16,
167 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21,
168 21, 22, 22, 22, 23, 23, 23, 0, 0, 0, 1, 1, 1, 2, 2, 2,
169 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8,
170 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13,
171 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18,
172 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 0,
173 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3,
174 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6,
175 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
176 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5,
177 5, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2,
178 2, 1, 1, 1, 1, 0, 0, 0, 0, 23, 23, 23, 22, 22, 22, 21,
179 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16,
180 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11,
181 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 5,
182 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0,
183 0, 23, 23, 23, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19,
184 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13,
185 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 8, 8,
186 8, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3,
187 2, 2, 2, 1, 1, 1, 0, 0, 0, 23, 23, 23, 22, 22, 22, 21,
188 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16,
189 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11,
190 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6,
191 6, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3,
192 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0,
193 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 24, 24, 24,
194 24, 24, 24, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 27,
195 27, 27, 27, 27, 28, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30,
196 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35,
197 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41,
198 41, 41, 42, 42, 42, 43, 43, 43, 44, 44, 44, 45, 45, 45, 46, 46,
199 46, 47, 47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51,
200 52, 52, 52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57,
201 57, 57, 58, 58, 58, 59, 59, 59, 60, 60, 60, 61, 61, 61, 62, 62,
202 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 66, 67, 67, 67,
203 68, 68, 68, 69, 69, 69, 70, 70, 70, 71, 71, 71, 72, 72, 72, 73,
204 73, 73, 74, 74, 74, 75, 75, 75, 76, 76, 76, 77, 77, 77, 78, 78,
205 78, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, 82,
206 82, 83, 83, 83, 83, 83, 84, 84, 84, 84, 84, 84, 85, 85, 85, 85,
207 85, 85, 86, 86, 86, 86, 86, 86, 86, 86, 87, 87, 87, 87, 87, 87,
208 87, 87, 87, 87, 87, 87, 87, 87, 86, 86, 86, 86, 86, 86, 86, 85,
209 85, 85, 85, 85, 85, 84, 84, 84, 84, 84, 84, 83, 83, 83, 83, 83,
210 82, 82, 82, 82, 82, 81, 81, 81, 81, 80, 80, 80, 80, 79, 79, 79,
211 78, 78, 78, 77, 77, 77, 76, 76, 76, 75, 75, 75, 74, 74, 74, 73,
212 73, 73, 72, 72, 72, 71, 71, 71, 70, 70, 70, 69, 69, 69, 68, 68,
213 68, 67, 67, 67, 66, 66, 66, 65, 65, 65, 64, 64, 64, 63, 63, 63,
214 62, 62, 62, 61, 61, 61, 60, 60, 60, 59, 59, 59, 58, 58, 58, 57,
215 57, 57, 56, 56, 56, 55, 55, 55, 54, 54, 54, 53, 53, 53, 52, 52,
216 52, 51, 51, 51, 50, 50, 50, 49, 49, 49, 48, 48, 48, 47, 47, 47,
217 46, 46, 46, 45, 45, 45, 44, 44, 44, 43, 43, 43, 42, 42, 42, 41,
218 41, 41, 40, 40, 40, 39, 39, 39, 38, 38, 38, 37, 37, 37, 36, 36,
219 36, 35, 35, 35, 34, 34, 34, 33, 33, 33, 32, 32, 32, 31, 31, 31,
220 30, 30, 30, 30, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 27,
221 27, 27, 26, 26, 26, 26, 26, 26, 25, 25, 25, 25, 25, 25, 24, 24,
222 24, 24, 24, 24, 24, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
223 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3,
224 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6,
225 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11,
226 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16,
227 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21,
228 22, 22, 22, 23, 23, 23, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3,
229 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8,
230 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13,
231 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19,
232 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23, 23, 0, 0,
233 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5,
234 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11,
235 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16,
236 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21,
237 22, 22, 22, 23, 23, 23, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2,
238 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5,
239 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7,
240 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6,
241 6, 6, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 3, 3,
242 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0,
243 23, 23, 23, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18,
244 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13,
245 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8,
246 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2,
247 2, 2, 1, 1, 1, 0, 0, 0, 23, 23, 23, 22, 22, 22, 21, 21,
248 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16,
249 15, 15, 15, 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10,
250 10, 10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 6, 6, 6, 5, 5,
251 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0,
252 23, 23, 23, 22, 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18,
253 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13,
254 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8,
255 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 5, 4, 4, 4,
256 4, 4, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 1, 1,
257 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
258 0, 0, 0, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25,
259 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28,
260 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33,
261 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38,
262 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 42, 43, 43, 43, 44,
263 44, 44, 45, 45, 45, 46, 46, 46, 47, 47, 47, 48, 48, 48, 49, 49,
264 49, 50, 50, 50, 51, 51, 51, 52, 52, 52, 53, 53, 53, 54, 54, 54,
265 55, 55, 55, 56, 56, 56, 57, 57, 57, 58, 58, 58, 59, 59, 59, 60,
266 60, 60, 61, 61, 61, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65,
267 65, 66, 66, 66, 67, 67, 67, 68, 68, 68, 69, 69, 69, 70, 70, 70,
268 71, 71, 71, 72, 72, 72, 73, 73, 73, 74, 74, 74, 75, 75, 75, 76,
269 76, 76, 77, 77, 77, 78, 78, 78, 79, 79, 79, 80, 80, 80, 80, 81,
270 81, 81, 81, 82, 82, 82, 82, 82, 83, 83, 83, 83, 83, 84, 84, 84,
271 84, 84, 84, 85, 85, 85, 85, 85, 85, 86, 86, 86, 86, 86, 86, 86,
272 86, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 87, 86,
273 86, 86, 86, 86, 86, 86, 85, 85, 85, 85, 85, 85, 84, 84, 84, 84,
274 84, 84, 83, 83, 83, 83, 83, 82, 82, 82, 82, 82, 81, 81, 81, 81,
275 80, 80, 80, 80, 79, 79, 79, 78, 78, 78, 77, 77, 77, 76, 76, 76,
276 75, 75, 75, 74, 74, 74, 73, 73, 73, 72, 72, 72, 71, 71, 71, 70,
277 70, 70, 69, 69, 69, 68, 68, 68, 67, 67, 67, 66, 66, 66, 65, 65,
278 65, 64, 64, 64, 63, 63, 63, 62, 62, 62, 61, 61, 61, 60, 60, 60,
279 59, 59, 59, 58, 58, 58, 57, 57, 57, 56, 56, 56, 55, 55, 55, 54,
280 54, 54, 53, 53, 53, 52, 52, 52, 51, 51, 51, 50, 50, 50, 49, 49,
281 49, 48, 48, 48, 47, 47, 47, 46, 46, 46, 45, 45, 45, 44, 44, 44,
282 43, 43, 43, 42, 42, 42, 41, 41, 41, 40, 40, 40, 39, 39, 39, 38,
283 38, 38, 37, 37, 37, 36, 36, 36, 35, 35, 35, 34, 34, 34, 33, 33,
284 33, 32, 32, 32, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 28,
285 28, 28, 28, 28, 27, 27, 27, 27, 27, 26, 26, 26, 26, 26, 26, 25,
286 25, 25, 25, 25, 25, 24, 24, 24, 24, 24, 24, 24, 24, 0,
287 255,
288 };
289 // clang-format on
290
291 static constexpr const int8_t SwingingTimeToSpriteMap_0[] = {
292 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3,
293 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2,
294 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -2, -2, -2, -2, -2,
295 -2, -2, -2, -2, -2, -2, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3,
296 -3, -3, -3, -3, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, -1, -1, 0, 0, -128,
297 };
298 static constexpr const int8_t SwingingTimeToSpriteMap_1[] = {
299 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5,
300 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3,
301 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, -1, -1, -1, -1, -2, -2, -2, -2, -2, -3, -3, -3, -3, -3,
302 -3, -4, -4, -4, -4, -4, -4, -4, -4, -4, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5, -5,
303 -5, -4, -4, -4, -4, -4, -4, -4, -4, -4, -3, -3, -3, -3, -3, -3, -2, -2, -2, -2, -2, -1, -1, -1, -1, 0, -128,
304 };
305 static constexpr const int8_t SwingingTimeToSpriteMap_2[] = {
306 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6,
307 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6,
308 6, 6, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0,
309 -1, -1, -1, -2, -2, -2, -3, -3, -3, -3, -4, -4, -4, -4, -4, -5, -5, -5, -5, -5, -5, -6, -6, -6, -6, -6,
310 -6, -6, -6, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -6, -6, -6, -6, -6, -6, -6, -6,
311 -5, -5, -5, -5, -5, -5, -4, -4, -4, -4, -4, -3, -3, -3, -3, -2, -2, -2, -1, -1, -1, 0, -128,
312 };
313 static constexpr const int8_t SwingingTimeToSpriteMap_3[] = {
314 0, 1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8,
315 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8,
316 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0, -1,
317 -1, -2, -2, -3, -3, -4, -4, -4, -5, -5, -5, -5, -6, -6, -6, -6, -6, -7, -7, -7, -7, -7, -7, -8, -8, -8, -8,
318 -8, -8, -8, -8, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -8, -8, -8, -8, -8, -8, -8, -8, -7, -7,
319 -7, -7, -7, -7, -6, -6, -6, -6, -6, -5, -5, -5, -5, -4, -4, -4, -3, -3, -2, -2, -1, -1, 0, -128,
320 };
321 static constexpr const int8_t SwingingTimeToSpriteMap_4[] = {
322 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5,
323 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
324 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5,
325 5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, -1,
326 -1, -1, -1, -1, -2, -2, -2, -2, -2, -3, -3, -3, -3, -3, -4, -4, -4, -4, -4, -5, -5, -5, -5, -5, -5, -5, -6, -6,
327 -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7, -7,
328 -7, -7, -7, -7, -7, -7, -7, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -6, -5, -5, -5, -5, -5, -5, -5, -4, -4,
329 -4, -4, -4, -3, -3, -3, -3, -3, -2, -2, -2, -2, -2, -1, -1, -1, -1, -1, 0, 0, -128,
330 };
331 static constexpr const int8_t SwingingTimeToSpriteMap_5[] = {
332 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6,
333 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12,
334 12, 12, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15,
335 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 14,
336 14, 14, 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 11, 11, 11, 11, 10, 10, 10, 10, 9, 9, 9, 9,
337 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3,
338 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, -1, -1, -1, -1, -2, -2, -2, -2, -3, -3, -3, -3,
339 -4, -4, -4, -4, -5, -5, -5, -5, -6, -6, -6, -6, -7, -7, -7, -7, -8, -8, -8, -8, -9, -9, -9, -9,
340 -10, -10, -10, -10, -11, -11, -11, -11, -12, -12, -12, -12, -13, -13, -13, -13, -13, -13, -14, -14, -14, -14, -14, -14,
341 -14, -14, -14, -14, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15, -15,
342 -15, -15, -14, -14, -14, -14, -14, -14, -14, -14, -14, -14, -13, -13, -13, -13, -13, -13, -12, -12, -12, -12, -11, -11,
343 -11, -11, -10, -10, -10, -10, -9, -9, -9, -9, -8, -8, -8, -8, -7, -7, -7, -7, -6, -6, -6, -6, -5, -5,
344 -5, -5, -4, -4, -4, -4, -3, -3, -3, -3, -2, -2, -2, -2, -1, -1, -1, -1, 0, 0, -128,
345 };
346 static constexpr const int8_t SwingingTimeToSpriteMap_6[] = {
347 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8,
348 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15,
349 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22, 22, 23, 23,
350 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
351 25, 25, 25, 25, 25, 25, 25, 25, 24, 24, 24, 24, 24, 24, 24, 24, 24, 23, 23, 23, 23, 23, 22,
352 22, 22, 21, 21, 21, 20, 20, 20, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16, 15, 15, 15,
353 14, 14, 14, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10, 9, 9, 9, 8, 8, 8, 7, 7,
354 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 0, 0, 0, -1,
355 -1, -1, -2, -2, -2, -3, -3, -3, -4, -4, -4, -5, -5, -5, -6, -6, -6, -7, -7, -7, -8, -8, -8,
356 -9, -9, -9, -10, -10, -10, -11, -11, -11, -12, -12, -12, -13, -13, -13, -14, -14, -14, -15, -15, -15, -16, -16,
357 -16, -17, -17, -17, -18, -18, -18, -19, -19, -19, -20, -20, -20, -21, -21, -21, -22, -22, -22, -23, -23, -23, -23,
358 -23, -24, -24, -24, -24, -24, -24, -24, -24, -24, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25, -25,
359 -25, -25, -25, -25, -25, -25, -24, -24, -24, -24, -24, -24, -24, -24, -24, -23, -23, -23, -23, -23, -22, -22, -22,
360 -21, -21, -21, -20, -20, -20, -19, -19, -19, -18, -18, -18, -17, -17, -17, -16, -16, -16, -15, -15, -15, -14, -14,
361 -14, -13, -13, -13, -12, -12, -12, -11, -11, -11, -10, -10, -10, -9, -9, -9, -8, -8, -8, -7, -7, -7, -6,
362 -6, -6, -5, -5, -5, -4, -4, -4, -3, -3, -3, -2, -2, -2, -1, -1, -1, 0, 0, -128,
363 };
364 static constexpr const int8_t SwingingTimeToSpriteMap_7[] = {
365 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7,
366 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15,
367 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 21, 22, 22,
368 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29,
369 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, -35,
370 -35, -35, -34, -34, -34, -33, -33, -33, -32, -32, -32, -31, -31, -31, -30, -30, -30, -29, -29, -29, -28, -28,
371 -28, -27, -27, -27, -26, -26, -26, -25, -25, -25, -24, -24, -24, -23, -23, -23, -22, -22, -22, -21, -21, -21,
372 -20, -20, -20, -19, -19, -19, -18, -18, -18, -17, -17, -17, -16, -16, -16, -15, -15, -15, -14, -14, -14, -13,
373 -13, -13, -12, -12, -12, -11, -11, -11, -10, -10, -10, -9, -9, -9, -8, -8, -8, -7, -7, -7, -6, -6,
374 -6, -5, -5, -5, -4, -4, -4, -3, -3, -3, -2, -2, -2, -1, -1, -1, 0, 0, -128,
375 };
376 static constexpr const int8_t SwingingTimeToSpriteMap_8[] = {
377 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5,
378 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
379 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5,
380 5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 31,
381 31, 31, 31, 31, 30, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 27, 27, 26, 26,
382 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
383 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 28, 28,
384 28, 28, 28, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 0, 0, -128,
385 };
386 static constexpr const int8_t SwingingTimeToSpriteMap_9[] = {
387 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5,
388 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
389 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5,
390 5, 5, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 31,
391 31, 31, 31, 31, 30, 30, 30, 30, 30, 29, 29, 29, 29, 29, 28, 28, 28, 28, 28, 27, 27, 27, 27, 27, 27, 27, 26, 26,
392 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
393 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 28, 28,
394 28, 28, 28, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 0, 0, -128,
395 };
396 static constexpr const int8_t SwingingTimeToSpriteMap_10[] = {
397 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
398 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14,
399 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
400 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
401 16, 16, 16, 16, 16, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 12, 12, 12, 12, 11,
402 11, 11, 11, 10, 10, 10, 10, 9, 9, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4,
403 4, 3, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 31, 31, 31, 31, 30, 30, 30, 30, 29, 29, 29, 29, 28,
404 28, 28, 28, 27, 27, 27, 27, 26, 26, 26, 26, 25, 25, 25, 25, 24, 24, 24, 24, 23, 23, 23, 23, 22, 22, 22, 22, 21, 21, 21,
405 21, 20, 20, 20, 20, 19, 19, 19, 19, 18, 18, 18, 18, 18, 18, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 16, 16, 16, 16, 16,
406 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18,
407 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25,
408 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31, 0, 0, -128,
409 };
410 static constexpr const int8_t SwingingTimeToSpriteMap_11[] = {
411 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8,
412 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
413 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23,
414 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 0, -128,
415 };
416
417 /** rct2: 0x0099F9D0 */
418 static constexpr const int8_t* SwingingTimeToSpriteMaps[] = {
419 SwingingTimeToSpriteMap_0, SwingingTimeToSpriteMap_1, SwingingTimeToSpriteMap_2, SwingingTimeToSpriteMap_3,
420 SwingingTimeToSpriteMap_4, SwingingTimeToSpriteMap_5, SwingingTimeToSpriteMap_6, SwingingTimeToSpriteMap_7,
421 SwingingTimeToSpriteMap_8, SwingingTimeToSpriteMap_9, SwingingTimeToSpriteMap_10, SwingingTimeToSpriteMap_11,
422 };
423
424 struct unk_9a36c4
425 {
426 int16_t x;
427 int16_t y;
428 uint32_t distance;
429 };
430
431 /** rct2: 0x009A36C4 */
432 static constexpr const unk_9a36c4 Unk9A36C4[] = {
433 { -1, 0, 8716 }, { -1, 0, 8716 }, { -1, 0, 8716 }, { -1, 1, 12327 }, { -1, 1, 12327 }, { -1, 1, 12327 },
434 { 0, 1, 8716 }, { -1, 1, 12327 }, { 0, 1, 8716 }, { 0, 1, 8716 }, { 0, 1, 8716 }, { 1, 1, 12327 },
435 { 1, 1, 12327 }, { 1, 1, 12327 }, { 1, 0, 8716 }, { 1, 1, 12327 }, { 1, 0, 8716 }, { 1, 0, 8716 },
436 { 1, 0, 8716 }, { 1, -1, 12327 }, { 1, -1, 12327 }, { 1, -1, 12327 }, { 0, -1, 8716 }, { 1, -1, 12327 },
437 { 0, -1, 8716 }, { 0, -1, 8716 }, { 0, -1, 8716 }, { -1, -1, 12327 }, { -1, -1, 12327 }, { -1, -1, 12327 },
438 { -1, 0, 8716 }, { -1, -1, 12327 },
439 };
440
441 /** rct2: 0x009A37C4 */
442 static constexpr const CoordsXY SurroundingTiles[] = {
443 { 0, 0 },
444 { 0, +COORDS_XY_STEP },
445 { +COORDS_XY_STEP, 0 },
446 { 0, -COORDS_XY_STEP },
447 { 0, -COORDS_XY_STEP },
448 { -COORDS_XY_STEP, 0 },
449 { -COORDS_XY_STEP, 0 },
450 { 0, +COORDS_XY_STEP },
451 { 0, +COORDS_XY_STEP },
452 };
453
454 /** rct2: 0x009A37E4 */
455 static constexpr const int32_t Unk9A37E4[] = {
456 2147483647, 2106585154, 1985590284, 1636362342, 1127484953, 2106585154, 1985590284, 1636362342, 1127484953,
457 58579923, 0, -555809667, -1073741824, -1518500249, -1859775391, -2074309916, -2147483647, 58579923,
458 0, -555809667, -1073741824, -1518500249, -1859775391, -2074309916, 1859775393, 1073741824, 0,
459 -1073741824, -1859775393, 1859775393, 1073741824, 0, -1073741824, -1859775393, 1859775393, 1073741824,
460 0, -1073741824, -1859775393, 1859775393, 1073741824, 0, -1073741824, -1859775393, 2144540595,
461 2139311823, 2144540595, 2139311823, 2135719507, 2135719507, 2125953864, 2061796213, 1411702590, 2125953864,
462 2061796213, 1411702590, 1985590284, 1636362342, 1127484953, 2115506168,
463 };
464
465 /** rct2: 0x009A38D4 */
466 static constexpr const int32_t Unk9A38D4[] = {
467 0, 417115092, 817995863, 1390684831, 1827693544, -417115092, -817995863, -1390684831, -1827693544,
468 2066040965, 2147483647, 2074309916, 1859775393, 1518500249, 1073741824, 555809666, 0, -2066040965,
469 -2147483647, -2074309916, -1859775393, -1518500249, -1073741824, -555809666, 1073741824, 1859775393, 2147483647,
470 1859775393, 1073741824, -1073741824, -1859775393, -2147483647, -1859775393, -1073741824, 1073741824, 1859775393,
471 2147483647, 1859775393, 1073741824, -1073741824, -1859775393, -2147483647, -1859775393, -1073741824, 112390610,
472 187165532, -112390610, -187165532, 224473165, -224473165, 303325208, 600568389, 1618265062, -303325208,
473 -600568389, -1618265062, -817995863, -1390684831, -1827693544, 369214930,
474 };
475
476 /** rct2: 0x009A39C4 */
477 static constexpr const int32_t Unk9A39C4[] = {
478 2147483647, 2096579710, 1946281152, 2096579710, 1946281152, 1380375879, 555809667, -372906620, -1231746017, -1859775391,
479 1380375879, 555809667, -372906620, -1231746017, -1859775391, 0, 2096579710, 1946281152, 2096579710, 1946281152,
480 };
481
482 static constexpr const CoordsXY AvoidCollisionMoveOffset[] = {
483 { -1, 0 },
484 { 0, 1 },
485 { 1, 0 },
486 { 0, -1 },
487 };
488
489 static constexpr const OpenRCT2::Audio::SoundId DoorOpenSoundIds[] = {
490 OpenRCT2::Audio::SoundId::DoorOpen,
491 OpenRCT2::Audio::SoundId::Portcullis,
492 };
493
494 static constexpr const OpenRCT2::Audio::SoundId DoorCloseSoundIds[] = {
495 OpenRCT2::Audio::SoundId::DoorClose,
496 OpenRCT2::Audio::SoundId::Portcullis,
497 };
498
499 static const struct
500 {
501 int8_t x, y, z;
502 } SteamParticleOffsets[][16] = {
503 {
504 { -11, 0, 22 },
505 { -10, 4, 22 },
506 { -8, 8, 22 },
507 { -4, 10, 22 },
508 { 0, 11, 22 },
509 { 4, 10, 22 },
510 { 8, 8, 22 },
511 { 10, 4, 22 },
512 { 11, 0, 22 },
513 { 10, -4, 22 },
514 { 8, -8, 22 },
515 { 4, -10, 22 },
516 { 0, -11, 22 },
517 { -4, -10, 22 },
518 { -8, -8, 22 },
519 { -10, -4, 22 },
520 },
521 {
522 { -9, 0, 27 },
523 { -8, 4, 27 },
524 { -6, 6, 27 },
525 { -4, 8, 27 },
526 { 0, 9, 27 },
527 { 4, 8, 27 },
528 { 6, 6, 27 },
529 { 8, 4, 27 },
530 { 9, 0, 27 },
531 { 8, -4, 27 },
532 { 6, -6, 27 },
533 { 4, -8, 27 },
534 { 0, -9, 27 },
535 { -4, -8, 27 },
536 { -6, -6, 27 },
537 { -8, -4, 27 },
538 },
539 {
540 { -13, 0, 18 },
541 { -12, 4, 17 },
542 { -9, 9, 17 },
543 { -4, 8, 17 },
544 { 0, 13, 18 },
545 { 4, 8, 17 },
546 { 6, 6, 17 },
547 { 8, 4, 17 },
548 { 13, 0, 18 },
549 { 8, -4, 17 },
550 { 6, -6, 17 },
551 { 4, -8, 17 },
552 { 0, -13, 18 },
553 { -4, -8, 17 },
554 { -6, -6, 17 },
555 { -8, -4, 17 },
556 },
557 };
558
Is() const559 template<> bool EntityBase::Is<Vehicle>() const
560 {
561 return Type == EntityType::Vehicle;
562 }
563
564 #ifdef ENABLE_SCRIPTING
565 /**
566 * Fires the "vehicle.crash" api hook
567 * @param vehicleId Entity id of the vehicle that just crashed
568 * @param crashId What the vehicle crashed into. Should be either "another_vehicle", "land", or "water"
569 */
InvokeVehicleCrashHook(const uint16_t vehicleId,const std::string_view crashId)570 static void InvokeVehicleCrashHook(const uint16_t vehicleId, const std::string_view crashId)
571 {
572 auto& hookEngine = OpenRCT2::GetContext()->GetScriptEngine().GetHookEngine();
573 if (hookEngine.HasSubscriptions(OpenRCT2::Scripting::HOOK_TYPE::VEHICLE_CRASH))
574 {
575 auto ctx = OpenRCT2::GetContext()->GetScriptEngine().GetContext();
576
577 // Create event args object
578 auto obj = OpenRCT2::Scripting::DukObject(ctx);
579 obj.Set("id", vehicleId);
580 obj.Set("crashIntoType", crashId);
581
582 // Call the subscriptions
583 auto e = obj.Take();
584 hookEngine.Call(OpenRCT2::Scripting::HOOK_TYPE::VEHICLE_CRASH, e, true);
585 }
586 }
587 #endif
588
vehicle_move_info_valid(VehicleTrackSubposition trackSubposition,track_type_t type,uint8_t direction,int32_t offset)589 static bool vehicle_move_info_valid(
590 VehicleTrackSubposition trackSubposition, track_type_t type, uint8_t direction, int32_t offset)
591 {
592 uint16_t typeAndDirection = (type << 2) | (direction & 3);
593
594 if (trackSubposition >= VehicleTrackSubposition{ std::size(gTrackVehicleInfo) })
595 {
596 return false;
597 }
598 int32_t size = 0;
599 switch (trackSubposition)
600 {
601 case VehicleTrackSubposition::Default:
602 size = VehicleTrackSubpositionSizeDefault;
603 break;
604 case VehicleTrackSubposition::ChairliftGoingOut:
605 size = 692;
606 break;
607 case VehicleTrackSubposition::ChairliftGoingBack:
608 case VehicleTrackSubposition::ChairliftEndBullwheel:
609 case VehicleTrackSubposition::ChairliftStartBullwheel:
610 size = 404;
611 break;
612 case VehicleTrackSubposition::GoKartsLeftLane:
613 case VehicleTrackSubposition::GoKartsRightLane:
614 case VehicleTrackSubposition::GoKartsMovingToRightLane:
615 case VehicleTrackSubposition::GoKartsMovingToLeftLane:
616 size = 208;
617 break;
618 case VehicleTrackSubposition::MiniGolfPathA9: // VehicleTrackSubposition::MiniGolfStart9
619 case VehicleTrackSubposition::MiniGolfBallPathA10:
620 case VehicleTrackSubposition::MiniGolfPathB11:
621 case VehicleTrackSubposition::MiniGolfBallPathB12:
622 case VehicleTrackSubposition::MiniGolfPathC13:
623 case VehicleTrackSubposition::MiniGolfBallPathC14:
624 size = 824;
625 break;
626 case VehicleTrackSubposition::ReverserRCFrontBogie:
627 case VehicleTrackSubposition::ReverserRCRearBogie:
628 size = 868;
629 break;
630 default:
631 break;
632 }
633 if (typeAndDirection >= size)
634 {
635 return false;
636 }
637 if (offset >= gTrackVehicleInfo[static_cast<uint8_t>(trackSubposition)][typeAndDirection]->size)
638 {
639 return false;
640 }
641 return true;
642 }
643
vehicle_get_move_info(VehicleTrackSubposition trackSubposition,track_type_t type,uint8_t direction,int32_t offset)644 static const rct_vehicle_info* vehicle_get_move_info(
645 VehicleTrackSubposition trackSubposition, track_type_t type, uint8_t direction, int32_t offset)
646 {
647 uint16_t typeAndDirection = (type << 2) | (direction & 3);
648
649 if (!vehicle_move_info_valid(trackSubposition, type, direction, offset))
650 {
651 static constexpr const rct_vehicle_info zero = {};
652 return &zero;
653 }
654 return &gTrackVehicleInfo[static_cast<uint8_t>(trackSubposition)][typeAndDirection]->info[offset];
655 }
656
GetMoveInfo() const657 const rct_vehicle_info* Vehicle::GetMoveInfo() const
658 {
659 return vehicle_get_move_info(TrackSubposition, GetTrackType(), GetTrackDirection(), track_progress);
660 }
661
vehicle_get_move_info_size(VehicleTrackSubposition trackSubposition,track_type_t type,uint8_t direction)662 static uint16_t vehicle_get_move_info_size(VehicleTrackSubposition trackSubposition, track_type_t type, uint8_t direction)
663 {
664 uint16_t typeAndDirection = (type << 2) | (direction & 3);
665
666 if (!vehicle_move_info_valid(trackSubposition, type, direction, 0))
667 {
668 return 0;
669 }
670 return gTrackVehicleInfo[static_cast<uint8_t>(trackSubposition)][typeAndDirection]->size;
671 }
672
GetTrackProgress() const673 uint16_t Vehicle::GetTrackProgress() const
674 {
675 return vehicle_get_move_info_size(TrackSubposition, GetTrackType(), GetTrackDirection());
676 }
677
ApplyMass(int16_t appliedMass)678 void Vehicle::ApplyMass(int16_t appliedMass)
679 {
680 mass = std::clamp<int32_t>(mass + appliedMass, 1, std::numeric_limits<decltype(mass)>::max());
681 }
682
MoveRelativeDistance(int32_t distance)683 void Vehicle::MoveRelativeDistance(int32_t distance)
684 {
685 remaining_distance += distance;
686
687 SetUpdateFlag(VEHICLE_UPDATE_FLAG_SINGLE_CAR_POSITION | VEHICLE_UPDATE_FLAG_COLLISION_DISABLED);
688 UpdateTrackMotion(nullptr);
689 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_SINGLE_CAR_POSITION | VEHICLE_UPDATE_FLAG_COLLISION_DISABLED);
690 }
691
try_get_vehicle(uint16_t spriteIndex)692 Vehicle* try_get_vehicle(uint16_t spriteIndex)
693 {
694 return TryGetEntity<Vehicle>(spriteIndex);
695 }
696
697 namespace
698 {
699 template<typename T> class TrainIterator;
700 template<typename T> class Train
701 {
702 public:
Train(T * vehicle)703 explicit Train(T* vehicle)
704 : FirstCar(vehicle)
705 {
706 assert(FirstCar->IsHead());
707 }
708 int32_t Mass();
709
710 friend class TrainIterator<T>;
711 using iterator = TrainIterator<T>;
begin()712 iterator begin()
713 {
714 return iterator{ FirstCar };
715 }
end()716 iterator end()
717 {
718 return iterator{};
719 }
720
721 private:
722 T* FirstCar;
723 };
724 template<typename T> class TrainIterator
725 {
726 public:
727 using iterator = TrainIterator;
728 using iterator_category = std::forward_iterator_tag;
729 using value_type = T;
730 using pointer = T*;
731 using reference = T&;
732
733 TrainIterator() = default;
TrainIterator(T * vehicle)734 explicit TrainIterator(T* vehicle)
735 : Current(vehicle)
736 {
737 }
operator *()738 reference operator*()
739 {
740 return *Current;
741 }
operator ++()742 iterator& operator++()
743 {
744 Current = GetEntity<Vehicle>(NextVehicleId);
745 if (Current != nullptr)
746 {
747 NextVehicleId = Current->next_vehicle_on_train;
748 }
749 return *this;
750 }
operator ++(int)751 iterator operator++(int)
752 {
753 iterator temp = *this;
754 ++*this;
755 return temp;
756 }
operator !=(const iterator & other)757 bool operator!=(const iterator& other)
758 {
759 return Current != other.Current;
760 }
761
762 private:
763 T* Current = nullptr;
764 uint16_t NextVehicleId = SPRITE_INDEX_NULL;
765 };
766 } // namespace
767
Mass()768 template<typename T> int32_t Train<T>::Mass()
769 {
770 int32_t totalMass = 0;
771 for (const auto& vehicle : *this)
772 {
773 totalMass += vehicle.mass;
774 }
775
776 return totalMass;
777 }
778
SoundCanPlay() const779 bool Vehicle::SoundCanPlay() const
780 {
781 if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
782 return false;
783
784 if ((gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) && gEditorStep != EditorStep::RollercoasterDesigner)
785 return false;
786
787 if (sound1_id == OpenRCT2::Audio::SoundId::Null && sound2_id == OpenRCT2::Audio::SoundId::Null)
788 return false;
789
790 if (x == LOCATION_NULL)
791 return false;
792
793 if (g_music_tracking_viewport == nullptr)
794 return false;
795
796 const auto quarter_w = g_music_tracking_viewport->view_width / 4;
797 const auto quarter_h = g_music_tracking_viewport->view_height / 4;
798
799 auto left = g_music_tracking_viewport->viewPos.x;
800 auto bottom = g_music_tracking_viewport->viewPos.y;
801
802 if (window_get_classification(gWindowAudioExclusive) == WC_MAIN_WINDOW)
803 {
804 left -= quarter_w;
805 bottom -= quarter_h;
806 }
807
808 if (left >= SpriteRect.GetRight() || bottom >= SpriteRect.GetBottom())
809 return false;
810
811 auto right = g_music_tracking_viewport->view_width + left;
812 auto top = g_music_tracking_viewport->view_height + bottom;
813
814 if (window_get_classification(gWindowAudioExclusive) == WC_MAIN_WINDOW)
815 {
816 right += quarter_w + quarter_w;
817 top += quarter_h + quarter_h;
818 }
819
820 if (right < SpriteRect.GetRight() || top < SpriteRect.GetTop())
821 return false;
822
823 return true;
824 }
825
826 /**
827 *
828 * rct2: 0x006BC2F3
829 */
GetSoundPriority() const830 uint16_t Vehicle::GetSoundPriority() const
831 {
832 int32_t result = Train(this).Mass() + (std::abs(velocity) >> 13);
833
834 for (const auto& vehicleSound : OpenRCT2::Audio::gVehicleSoundList)
835 {
836 if (vehicleSound.id == sprite_index)
837 {
838 // Vehicle sounds will get higher priority if they are already playing
839 return result + 300;
840 }
841 }
842
843 return result;
844 }
845
CreateSoundParam(uint16_t priority) const846 OpenRCT2::Audio::VehicleSoundParams Vehicle::CreateSoundParam(uint16_t priority) const
847 {
848 OpenRCT2::Audio::VehicleSoundParams param;
849 param.priority = priority;
850 int32_t panX = (SpriteRect.GetLeft() / 2) + (SpriteRect.GetRight() / 2) - g_music_tracking_viewport->viewPos.x;
851 panX = panX / g_music_tracking_viewport->zoom;
852 panX += g_music_tracking_viewport->pos.x;
853
854 uint16_t screenWidth = context_get_width();
855 if (screenWidth < 64)
856 {
857 screenWidth = 64;
858 }
859 param.pan_x = ((((panX * 65536) / screenWidth) - 0x8000) >> 4);
860
861 int32_t panY = (SpriteRect.GetTop() / 2) + (SpriteRect.GetBottom() / 2) - g_music_tracking_viewport->viewPos.y;
862 panY = panY / g_music_tracking_viewport->zoom;
863 panY += g_music_tracking_viewport->pos.y;
864
865 uint16_t screenHeight = context_get_height();
866 if (screenHeight < 64)
867 {
868 screenHeight = 64;
869 }
870 param.pan_y = ((((panY * 65536) / screenHeight) - 0x8000) >> 4);
871
872 int32_t frequency = std::abs(velocity);
873
874 rct_ride_entry* rideType = GetRideEntry();
875 if (rideType != nullptr)
876 {
877 if (rideType->vehicles[vehicle_type].double_sound_frequency & 1)
878 {
879 frequency *= 2;
880 }
881 }
882
883 // * 0.0105133...
884 frequency >>= 5; // /32
885 frequency *= 5512;
886 frequency >>= 14; // /16384
887
888 frequency += 11025;
889 frequency += 16 * sound_vector_factor;
890 param.frequency = static_cast<uint16_t>(frequency);
891 param.id = sprite_index;
892 param.volume = 0;
893
894 if (x != LOCATION_NULL)
895 {
896 auto surfaceElement = map_get_surface_element_at(CoordsXY{ x, y });
897
898 // vehicle underground
899 if (surfaceElement != nullptr && surfaceElement->GetBaseZ() > z)
900 {
901 param.volume = 0x30;
902 }
903 }
904 return param;
905 }
906
907 /**
908 *
909 * rct2: 0x006BB9FF
910 */
UpdateSoundParams(std::vector<OpenRCT2::Audio::VehicleSoundParams> & vehicleSoundParamsList) const911 void Vehicle::UpdateSoundParams(std::vector<OpenRCT2::Audio::VehicleSoundParams>& vehicleSoundParamsList) const
912 {
913 if (!SoundCanPlay())
914 return;
915
916 uint16_t soundPriority = GetSoundPriority();
917 // Find a sound param of lower priority to use
918 auto soundParamIter = std::find_if(
919 vehicleSoundParamsList.begin(), vehicleSoundParamsList.end(),
920 [soundPriority](const auto& param) { return soundPriority > param.priority; });
921
922 if (soundParamIter == std::end(vehicleSoundParamsList))
923 {
924 if (vehicleSoundParamsList.size() < OpenRCT2::Audio::MaxVehicleSounds)
925 {
926 vehicleSoundParamsList.push_back(CreateSoundParam(soundPriority));
927 }
928 }
929 else
930 {
931 if (vehicleSoundParamsList.size() < OpenRCT2::Audio::MaxVehicleSounds)
932 {
933 // Shift all sound params down one if using a free space
934 vehicleSoundParamsList.insert(soundParamIter, CreateSoundParam(soundPriority));
935 }
936 else
937 {
938 *soundParamIter = CreateSoundParam(soundPriority);
939 }
940 }
941 }
942
vehicle_sounds_update_window_setup()943 static void vehicle_sounds_update_window_setup()
944 {
945 g_music_tracking_viewport = nullptr;
946
947 rct_window* window = window_get_listening();
948 if (window == nullptr)
949 {
950 return;
951 }
952
953 rct_viewport* viewport = window_get_viewport(window);
954 if (viewport == nullptr)
955 {
956 return;
957 }
958
959 g_music_tracking_viewport = viewport;
960 gWindowAudioExclusive = window;
961 if (viewport->zoom <= 0)
962 OpenRCT2::Audio::gVolumeAdjustZoom = 0;
963 else if (viewport->zoom == 1)
964 OpenRCT2::Audio::gVolumeAdjustZoom = 35;
965 else
966 OpenRCT2::Audio::gVolumeAdjustZoom = 70;
967 }
968
vehicle_sounds_update_get_pan_volume(OpenRCT2::Audio::VehicleSoundParams * sound_params)969 static uint8_t vehicle_sounds_update_get_pan_volume(OpenRCT2::Audio::VehicleSoundParams* sound_params)
970 {
971 uint8_t vol1 = 0xFF;
972 uint8_t vol2 = 0xFF;
973
974 int16_t pan_y = std::abs(sound_params->pan_y);
975 pan_y = std::min(static_cast<int16_t>(0xFFF), pan_y);
976 pan_y -= 0x800;
977 if (pan_y > 0)
978 {
979 pan_y = (0x400 - pan_y) / 4;
980 vol1 = LOBYTE(pan_y);
981 if (static_cast<int8_t>(HIBYTE(pan_y)) != 0)
982 {
983 vol1 = 0xFF;
984 if (static_cast<int8_t>(HIBYTE(pan_y)) < 0)
985 {
986 vol1 = 0;
987 }
988 }
989 }
990
991 int16_t pan_x = std::abs(sound_params->pan_x);
992 pan_x = std::min(static_cast<int16_t>(0xFFF), pan_x);
993 pan_x -= 0x800;
994
995 if (pan_x > 0)
996 {
997 pan_x = (0x400 - pan_x) / 4;
998 vol2 = LOBYTE(pan_x);
999 if (static_cast<int8_t>(HIBYTE(pan_x)) != 0)
1000 {
1001 vol2 = 0xFF;
1002 if (static_cast<int8_t>(HIBYTE(pan_x)) < 0)
1003 {
1004 vol2 = 0;
1005 }
1006 }
1007 }
1008
1009 vol1 = std::min(vol1, vol2);
1010 return std::max(0, vol1 - OpenRCT2::Audio::gVolumeAdjustZoom);
1011 }
1012
1013 /* Returns the vehicle sound for a sound_param.
1014 *
1015 * If already playing returns sound.
1016 * If not playing allocates a sound slot to sound_param->id.
1017 * If no free slots returns nullptr.
1018 */
vehicle_sounds_update_get_vehicle_sound(OpenRCT2::Audio::VehicleSoundParams * sound_params)1019 static OpenRCT2::Audio::VehicleSound* vehicle_sounds_update_get_vehicle_sound(OpenRCT2::Audio::VehicleSoundParams* sound_params)
1020 {
1021 // Search for already playing vehicle sound
1022 for (auto& vehicleSound : OpenRCT2::Audio::gVehicleSoundList)
1023 {
1024 if (vehicleSound.id == sound_params->id)
1025 return &vehicleSound;
1026 }
1027
1028 // No sound already playing
1029 for (auto& vehicleSound : OpenRCT2::Audio::gVehicleSoundList)
1030 {
1031 // Use free slot
1032 if (vehicleSound.id == OpenRCT2::Audio::SoundIdNull)
1033 {
1034 vehicleSound.id = sound_params->id;
1035 vehicleSound.TrackSound.Id = OpenRCT2::Audio::SoundId::Null;
1036 vehicleSound.OtherSound.Id = OpenRCT2::Audio::SoundId::Null;
1037 vehicleSound.volume = 0x30;
1038 return &vehicleSound;
1039 }
1040 }
1041
1042 return nullptr;
1043 }
1044
1045 enum class SoundType
1046 {
1047 TrackNoises,
1048 OtherNoises, // e.g. Screams
1049 };
1050
SoundFrequency(const OpenRCT2::Audio::SoundId id,uint16_t baseFrequency)1051 template<SoundType type> static uint16_t SoundFrequency(const OpenRCT2::Audio::SoundId id, uint16_t baseFrequency)
1052 {
1053 if constexpr (type == SoundType::TrackNoises)
1054 {
1055 if (_soundParams[static_cast<uint8_t>(id)][1] & 2)
1056 {
1057 return (baseFrequency / 2) + 4000;
1058 }
1059 return baseFrequency;
1060 }
1061 else
1062 {
1063 if (_soundParams[static_cast<uint8_t>(id)][1] & 1)
1064 {
1065 return 22050;
1066 }
1067 return std::min((baseFrequency * 2) - 3248, 25700);
1068 }
1069 }
1070
ShouldUpdateChannelRate(const OpenRCT2::Audio::SoundId id)1071 template<SoundType type> static bool ShouldUpdateChannelRate(const OpenRCT2::Audio::SoundId id)
1072 {
1073 return type == SoundType::TrackNoises || !(_soundParams[static_cast<uint8_t>(id)][1] & 1);
1074 }
1075
1076 template<SoundType type>
UpdateSound(const OpenRCT2::Audio::SoundId id,int32_t volume,OpenRCT2::Audio::VehicleSoundParams * sound_params,OpenRCT2::Audio::Sound & sound,uint8_t panVol)1077 static void UpdateSound(
1078 const OpenRCT2::Audio::SoundId id, int32_t volume, OpenRCT2::Audio::VehicleSoundParams* sound_params,
1079 OpenRCT2::Audio::Sound& sound, uint8_t panVol)
1080 {
1081 volume *= panVol;
1082 volume = volume / 8;
1083 volume = std::max(volume - 0x1FFF, -10000);
1084
1085 if (id == OpenRCT2::Audio::SoundId::Null)
1086 {
1087 if (sound.Id != OpenRCT2::Audio::SoundId::Null)
1088 {
1089 sound.Id = OpenRCT2::Audio::SoundId::Null;
1090 Mixer_Stop_Channel(sound.Channel);
1091 }
1092 return;
1093 }
1094
1095 if (sound.Id != OpenRCT2::Audio::SoundId::Null && id != sound.Id)
1096 {
1097 Mixer_Stop_Channel(sound.Channel);
1098 }
1099
1100 if ((sound.Id == OpenRCT2::Audio::SoundId::Null) || (id != sound.Id))
1101 {
1102 sound.Id = id;
1103 sound.Pan = sound_params->pan_x;
1104 sound.Volume = volume;
1105 sound.Frequency = sound_params->frequency;
1106 uint16_t frequency = SoundFrequency<type>(id, sound_params->frequency);
1107 uint8_t looping = _soundParams[static_cast<uint8_t>(id)][0];
1108 int32_t pan = sound_params->pan_x;
1109 sound.Channel = Mixer_Play_Effect(
1110 id, looping ? MIXER_LOOP_INFINITE : MIXER_LOOP_NONE, DStoMixerVolume(volume), DStoMixerPan(pan),
1111 DStoMixerRate(frequency), 0);
1112 return;
1113 }
1114 if (volume != sound.Volume)
1115 {
1116 sound.Volume = volume;
1117 Mixer_Channel_Volume(sound.Channel, DStoMixerVolume(volume));
1118 }
1119 if (sound_params->pan_x != sound.Pan)
1120 {
1121 sound.Pan = sound_params->pan_x;
1122 Mixer_Channel_Pan(sound.Channel, DStoMixerPan(sound_params->pan_x));
1123 }
1124 if (!(gCurrentTicks & 3) && sound_params->frequency != sound.Frequency)
1125 {
1126 sound.Frequency = sound_params->frequency;
1127 if (ShouldUpdateChannelRate<type>(id))
1128 {
1129 uint16_t frequency = SoundFrequency<type>(id, sound_params->frequency);
1130 Mixer_Channel_Rate(sound.Channel, DStoMixerRate(frequency));
1131 }
1132 }
1133 }
1134
1135 /**
1136 *
1137 * rct2: 0x006BBC6B
1138 */
vehicle_sounds_update()1139 void vehicle_sounds_update()
1140 {
1141 if (!OpenRCT2::Audio::IsAvailable())
1142 return;
1143
1144 std::vector<OpenRCT2::Audio::VehicleSoundParams> vehicleSoundParamsList;
1145 vehicleSoundParamsList.reserve(OpenRCT2::Audio::MaxVehicleSounds);
1146
1147 vehicle_sounds_update_window_setup();
1148
1149 for (auto vehicle : TrainManager::View())
1150 {
1151 vehicle->UpdateSoundParams(vehicleSoundParamsList);
1152 }
1153
1154 // Stop all playing sounds that no longer have priority to play after vehicle_update_sound_params
1155 for (auto& vehicle_sound : OpenRCT2::Audio::gVehicleSoundList)
1156 {
1157 if (vehicle_sound.id != OpenRCT2::Audio::SoundIdNull)
1158 {
1159 bool keepPlaying = false;
1160 for (auto vehicleSoundParams : vehicleSoundParamsList)
1161 {
1162 if (vehicle_sound.id == vehicleSoundParams.id)
1163 {
1164 keepPlaying = true;
1165 break;
1166 }
1167 }
1168
1169 if (keepPlaying)
1170 continue;
1171
1172 if (vehicle_sound.TrackSound.Id != OpenRCT2::Audio::SoundId::Null)
1173 {
1174 Mixer_Stop_Channel(vehicle_sound.TrackSound.Channel);
1175 }
1176 if (vehicle_sound.OtherSound.Id != OpenRCT2::Audio::SoundId::Null)
1177 {
1178 Mixer_Stop_Channel(vehicle_sound.OtherSound.Channel);
1179 }
1180 vehicle_sound.id = OpenRCT2::Audio::SoundIdNull;
1181 }
1182 }
1183
1184 for (auto& vehicleSoundParams : vehicleSoundParamsList)
1185 {
1186 uint8_t panVol = vehicle_sounds_update_get_pan_volume(&vehicleSoundParams);
1187
1188 auto* vehicleSound = vehicle_sounds_update_get_vehicle_sound(&vehicleSoundParams);
1189 // No free vehicle sound slots (RCT2 corrupts the pointer here)
1190 if (vehicleSound == nullptr)
1191 continue;
1192
1193 // Move the Sound Volume towards the SoundsParam Volume
1194 int32_t tempvolume = vehicleSound->volume;
1195 if (tempvolume != vehicleSoundParams.volume)
1196 {
1197 if (tempvolume < vehicleSoundParams.volume)
1198 {
1199 tempvolume += 4;
1200 }
1201 else
1202 {
1203 tempvolume -= 4;
1204 }
1205 }
1206 vehicleSound->volume = tempvolume;
1207 panVol = std::max(0, panVol - tempvolume);
1208
1209 Vehicle* vehicle = GetEntity<Vehicle>(vehicleSoundParams.id);
1210 if (vehicle != nullptr)
1211 {
1212 UpdateSound<SoundType::TrackNoises>(
1213 vehicle->sound1_id, vehicle->sound1_volume, &vehicleSoundParams, vehicleSound->TrackSound, panVol);
1214 UpdateSound<SoundType::OtherNoises>(
1215 vehicle->sound2_id, vehicle->sound2_volume, &vehicleSoundParams, vehicleSound->OtherSound, panVol);
1216 }
1217 }
1218 }
1219
1220 /**
1221 *
1222 * rct2: 0x006D4204
1223 */
vehicle_update_all()1224 void vehicle_update_all()
1225 {
1226 if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
1227 return;
1228
1229 if ((gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) && gEditorStep != EditorStep::RollercoasterDesigner)
1230 return;
1231
1232 for (auto vehicle : TrainManager::View())
1233 {
1234 vehicle->Update();
1235 }
1236 }
1237
1238 /**
1239 *
1240 * rct2: 0x006D6956
1241 * @returns true when all closed
1242 */
CloseRestraints()1243 bool Vehicle::CloseRestraints()
1244 {
1245 auto curRide = GetRide();
1246 if (curRide == nullptr)
1247 return true;
1248
1249 bool restraintsClosed = true;
1250 for (Vehicle* vehicle = GetEntity<Vehicle>(sprite_index); vehicle != nullptr;
1251 vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
1252 {
1253 if (vehicle->HasUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_CAR) && vehicle->restraints_position != 0
1254 && (curRide->breakdown_reason_pending == BREAKDOWN_RESTRAINTS_STUCK_OPEN
1255 || curRide->breakdown_reason_pending == BREAKDOWN_DOORS_STUCK_OPEN))
1256 {
1257 if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN))
1258 {
1259 curRide->lifecycle_flags |= RIDE_LIFECYCLE_BROKEN_DOWN;
1260
1261 ride_breakdown_add_news_item(curRide);
1262
1263 curRide->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST
1264 | RIDE_INVALIDATE_RIDE_MAINTENANCE;
1265
1266 curRide->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
1267
1268 Vehicle* broken_vehicle = GetEntity<Vehicle>(curRide->vehicles[curRide->broken_vehicle]);
1269 if (broken_vehicle != nullptr)
1270 {
1271 curRide->inspection_station = broken_vehicle->current_station;
1272 }
1273 curRide->breakdown_reason = curRide->breakdown_reason_pending;
1274 }
1275 }
1276 else
1277 {
1278 vehicle->restraints_position = std::max(vehicle->restraints_position - 20, 0);
1279 if (vehicle->restraints_position == 0)
1280 {
1281 continue;
1282 }
1283 }
1284 vehicle->Invalidate();
1285 restraintsClosed = false;
1286 }
1287
1288 return restraintsClosed;
1289 }
1290
1291 /**
1292 *
1293 * rct2: 0x006D6A2C
1294 * @returns true when all open
1295 */
OpenRestraints()1296 bool Vehicle::OpenRestraints()
1297 {
1298 int32_t restraintsOpen = true;
1299 for (Vehicle* vehicle = GetEntity<Vehicle>(sprite_index); vehicle != nullptr;
1300 vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
1301 {
1302 vehicle->SwingPosition = 0;
1303 vehicle->SwingSpeed = 0;
1304 vehicle->SwingSprite = 0;
1305
1306 auto curRide = vehicle->GetRide();
1307 if (curRide == nullptr)
1308 continue;
1309
1310 auto rideEntry = vehicle->GetRideEntry();
1311 if (rideEntry == nullptr)
1312 {
1313 continue;
1314 }
1315
1316 rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle->vehicle_type];
1317
1318 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_SPINNING)
1319 {
1320 // If the vehicle is a spinner it must be spinning slow
1321 // For vehicles without additional frames there are 4 rotations it can unload from
1322 // For vehicles with additional frames it must be facing forward
1323 if (abs(vehicle->spin_speed) <= VEHICLE_MAX_SPIN_SPEED_FOR_STOPPING && !(vehicle->spin_sprite & 0x30)
1324 && (!(vehicleEntry->flags & VEHICLE_ENTRY_FLAG_SPINNING_ADDITIONAL_FRAMES) || !(vehicle->spin_sprite & 0xF8)))
1325 {
1326 vehicle->spin_speed = 0;
1327 }
1328 else
1329 {
1330 restraintsOpen = false;
1331
1332 if (abs(vehicle->spin_speed) < VEHICLE_STOPPING_SPIN_SPEED)
1333 {
1334 // Note will look odd if spinning right.
1335 vehicle->spin_speed = VEHICLE_STOPPING_SPIN_SPEED;
1336 }
1337 int16_t value = vehicle->spin_speed / 256;
1338 vehicle->spin_sprite += value;
1339 vehicle->spin_speed -= value;
1340
1341 vehicle->Invalidate();
1342 continue;
1343 }
1344 }
1345 if (vehicleEntry->animation == VEHICLE_ENTRY_ANIMATION_OBSERVATION_TOWER && vehicle->animation_frame != 0)
1346 {
1347 if (vehicle->animationState <= 0xCCCC)
1348 {
1349 vehicle->animationState += 0x3333;
1350 }
1351 else
1352 {
1353 vehicle->animationState = 0;
1354 vehicle->animation_frame++;
1355 vehicle->animation_frame &= 7;
1356 vehicle->Invalidate();
1357 }
1358 restraintsOpen = false;
1359 continue;
1360 }
1361 if (vehicleEntry->animation == VEHICLE_ENTRY_ANIMATION_ANIMAL_FLYING
1362 && (vehicle->animation_frame != 0 || vehicle->animationState > 0))
1363 {
1364 vehicle->UpdateAnimationAnimalFlying();
1365 restraintsOpen = false;
1366 continue;
1367 }
1368
1369 if (vehicle->HasUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_CAR) && vehicle->restraints_position != 0xFF
1370 && (curRide->breakdown_reason_pending == BREAKDOWN_RESTRAINTS_STUCK_CLOSED
1371 || curRide->breakdown_reason_pending == BREAKDOWN_DOORS_STUCK_CLOSED))
1372 {
1373 if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN))
1374 {
1375 curRide->lifecycle_flags |= RIDE_LIFECYCLE_BROKEN_DOWN;
1376
1377 ride_breakdown_add_news_item(curRide);
1378
1379 curRide->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST
1380 | RIDE_INVALIDATE_RIDE_MAINTENANCE;
1381
1382 curRide->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
1383
1384 Vehicle* broken_vehicle = GetEntity<Vehicle>(curRide->vehicles[curRide->broken_vehicle]);
1385 if (broken_vehicle != nullptr)
1386 {
1387 curRide->inspection_station = broken_vehicle->current_station;
1388 }
1389 curRide->breakdown_reason = curRide->breakdown_reason_pending;
1390 }
1391 }
1392 else
1393 {
1394 if (vehicle->restraints_position + 20 > 0xFF)
1395 {
1396 vehicle->restraints_position = 255;
1397 continue;
1398 }
1399 vehicle->restraints_position += 20;
1400 }
1401 vehicle->Invalidate();
1402 restraintsOpen = false;
1403 }
1404
1405 return restraintsOpen;
1406 }
1407
1408 /**
1409 *
1410 * rct2: 0x006D6D1F
1411 */
UpdateMeasurements()1412 void Vehicle::UpdateMeasurements()
1413 {
1414 auto curRide = GetRide();
1415 if (curRide == nullptr)
1416 return;
1417
1418 if (status == Vehicle::Status::TravellingBoat)
1419 {
1420 curRide->lifecycle_flags |= RIDE_LIFECYCLE_TESTED;
1421 curRide->lifecycle_flags |= RIDE_LIFECYCLE_NO_RAW_STATS;
1422 curRide->lifecycle_flags &= ~RIDE_LIFECYCLE_TEST_IN_PROGRESS;
1423 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_TESTING);
1424 window_invalidate_by_number(WC_RIDE, EnumValue(ride));
1425 return;
1426 }
1427
1428 if (curRide->current_test_station == STATION_INDEX_NULL)
1429 return;
1430
1431 if (!ride_get_entrance_location(curRide, curRide->current_test_station).IsNull())
1432 {
1433 uint8_t test_segment = curRide->current_test_segment;
1434
1435 curRide->average_speed_test_timeout++;
1436 if (curRide->average_speed_test_timeout >= 32)
1437 curRide->average_speed_test_timeout = 0;
1438
1439 int32_t absVelocity = abs(velocity);
1440 if (absVelocity > curRide->max_speed)
1441 {
1442 curRide->max_speed = absVelocity;
1443 }
1444
1445 if (curRide->average_speed_test_timeout == 0 && absVelocity > 0x8000)
1446 {
1447 curRide->average_speed = add_clamp_int32_t(curRide->average_speed, absVelocity);
1448 curRide->stations[test_segment].SegmentTime++;
1449 }
1450
1451 int32_t distance = abs(((velocity + acceleration) >> 10) * 42);
1452 if (var_CE == 0)
1453 {
1454 curRide->stations[test_segment].SegmentLength = add_clamp_int32_t(
1455 curRide->stations[test_segment].SegmentLength, distance);
1456 }
1457
1458 if (curRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_G_FORCES))
1459 {
1460 auto gForces = GetGForces();
1461 gForces.VerticalG += curRide->previous_vertical_g;
1462 gForces.LateralG += curRide->previous_lateral_g;
1463 gForces.VerticalG /= 2;
1464 gForces.LateralG /= 2;
1465
1466 curRide->previous_vertical_g = gForces.VerticalG;
1467 curRide->previous_lateral_g = gForces.LateralG;
1468 if (gForces.VerticalG <= 0)
1469 {
1470 curRide->total_air_time++;
1471 }
1472
1473 if (gForces.VerticalG > curRide->max_positive_vertical_g)
1474 curRide->max_positive_vertical_g = gForces.VerticalG;
1475
1476 if (gForces.VerticalG < curRide->max_negative_vertical_g)
1477 curRide->max_negative_vertical_g = gForces.VerticalG;
1478
1479 gForces.LateralG = std::abs(gForces.LateralG);
1480 curRide->max_lateral_g = std::max(curRide->max_lateral_g, static_cast<fixed16_2dp>(gForces.LateralG));
1481 }
1482 }
1483
1484 // If we have already evaluated this track piece skip to next section
1485 TileCoordsXYZ curTrackLoc{ TrackLocation };
1486 if (curTrackLoc != curRide->CurTestTrackLocation)
1487 {
1488 curRide->CurTestTrackLocation = curTrackLoc;
1489
1490 if (ride_get_entrance_location(curRide, curRide->current_test_station).IsNull())
1491 return;
1492
1493 auto trackElemType = GetTrackType();
1494 if (trackElemType == TrackElemType::PoweredLift || HasUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL))
1495 {
1496 if (!(curRide->testing_flags & RIDE_TESTING_POWERED_LIFT))
1497 {
1498 curRide->testing_flags |= RIDE_TESTING_POWERED_LIFT;
1499 if (curRide->drops + 64 < 0xFF)
1500 {
1501 curRide->drops += 64;
1502 }
1503 }
1504 }
1505 else
1506 {
1507 curRide->testing_flags &= ~RIDE_TESTING_POWERED_LIFT;
1508 }
1509
1510 if (curRide->type == RIDE_TYPE_WATER_COASTER)
1511 {
1512 if (trackElemType >= TrackElemType::FlatCovered && trackElemType <= TrackElemType::RightQuarterTurn3TilesCovered)
1513 {
1514 curRide->special_track_elements |= RIDE_ELEMENT_TUNNEL_SPLASH_OR_RAPIDS;
1515 }
1516 }
1517
1518 switch (trackElemType)
1519 {
1520 case TrackElemType::Rapids:
1521 case TrackElemType::SpinningTunnel:
1522 curRide->special_track_elements |= RIDE_ELEMENT_TUNNEL_SPLASH_OR_RAPIDS;
1523 break;
1524 case TrackElemType::Waterfall:
1525 case TrackElemType::LogFlumeReverser:
1526 curRide->special_track_elements |= RIDE_ELEMENT_REVERSER_OR_WATERFALL;
1527 break;
1528 case TrackElemType::Whirlpool:
1529 curRide->special_track_elements |= RIDE_ELEMENT_WHIRLPOOL;
1530 break;
1531 case TrackElemType::Watersplash:
1532 if (velocity >= 0xB0000)
1533 {
1534 curRide->special_track_elements |= RIDE_ELEMENT_TUNNEL_SPLASH_OR_RAPIDS;
1535 }
1536 }
1537
1538 const auto& ted = GetTrackElementDescriptor(trackElemType);
1539 uint16_t trackFlags = ted.Flags;
1540
1541 uint32_t testingFlags = curRide->testing_flags;
1542 if (testingFlags & RIDE_TESTING_TURN_LEFT && trackFlags & TRACK_ELEM_FLAG_TURN_LEFT)
1543 {
1544 // 0x800 as this is masked to CURRENT_TURN_COUNT_MASK
1545 curRide->turn_count_default += 0x800;
1546 }
1547 else if (testingFlags & RIDE_TESTING_TURN_RIGHT && trackFlags & TRACK_ELEM_FLAG_TURN_RIGHT)
1548 {
1549 // 0x800 as this is masked to CURRENT_TURN_COUNT_MASK
1550 curRide->turn_count_default += 0x800;
1551 }
1552 else if (testingFlags & RIDE_TESTING_TURN_RIGHT || testingFlags & RIDE_TESTING_TURN_LEFT)
1553 {
1554 curRide->testing_flags &= ~(
1555 RIDE_TESTING_TURN_LEFT | RIDE_TESTING_TURN_RIGHT | RIDE_TESTING_TURN_BANKED | RIDE_TESTING_TURN_SLOPED);
1556
1557 uint8_t turnType = 1;
1558 if (!(testingFlags & RIDE_TESTING_TURN_BANKED))
1559 {
1560 turnType = 2;
1561 if (!(testingFlags & RIDE_TESTING_TURN_SLOPED))
1562 {
1563 turnType = 0;
1564 }
1565 }
1566 switch (curRide->turn_count_default >> 11)
1567 {
1568 case 0:
1569 increment_turn_count_1_element(curRide, turnType);
1570 break;
1571 case 1:
1572 increment_turn_count_2_elements(curRide, turnType);
1573 break;
1574 case 2:
1575 increment_turn_count_3_elements(curRide, turnType);
1576 break;
1577 default:
1578 increment_turn_count_4_plus_elements(curRide, turnType);
1579 break;
1580 }
1581 }
1582 else
1583 {
1584 if (trackFlags & TRACK_ELEM_FLAG_TURN_LEFT)
1585 {
1586 curRide->testing_flags |= RIDE_TESTING_TURN_LEFT;
1587 curRide->turn_count_default &= ~CURRENT_TURN_COUNT_MASK;
1588
1589 if (trackFlags & TRACK_ELEM_FLAG_TURN_BANKED)
1590 {
1591 curRide->testing_flags |= RIDE_TESTING_TURN_BANKED;
1592 }
1593 if (trackFlags & TRACK_ELEM_FLAG_TURN_SLOPED)
1594 {
1595 curRide->testing_flags |= RIDE_TESTING_TURN_SLOPED;
1596 }
1597 }
1598
1599 if (trackFlags & TRACK_ELEM_FLAG_TURN_RIGHT)
1600 {
1601 curRide->testing_flags |= RIDE_TESTING_TURN_RIGHT;
1602 curRide->turn_count_default &= ~CURRENT_TURN_COUNT_MASK;
1603
1604 if (trackFlags & TRACK_ELEM_FLAG_TURN_BANKED)
1605 {
1606 curRide->testing_flags |= RIDE_TESTING_TURN_BANKED;
1607 }
1608 if (trackFlags & TRACK_ELEM_FLAG_TURN_SLOPED)
1609 {
1610 curRide->testing_flags |= RIDE_TESTING_TURN_SLOPED;
1611 }
1612 }
1613 }
1614
1615 if (testingFlags & RIDE_TESTING_DROP_DOWN)
1616 {
1617 if (velocity < 0 || !(trackFlags & TRACK_ELEM_FLAG_DOWN))
1618 {
1619 curRide->testing_flags &= ~RIDE_TESTING_DROP_DOWN;
1620
1621 int16_t curZ = z / COORDS_Z_STEP - curRide->start_drop_height;
1622 if (curZ < 0)
1623 {
1624 curZ = abs(curZ);
1625 if (curZ > curRide->highest_drop_height)
1626 {
1627 curRide->highest_drop_height = static_cast<uint8_t>(curZ);
1628 }
1629 }
1630 }
1631 }
1632 else if (trackFlags & TRACK_ELEM_FLAG_DOWN && velocity >= 0)
1633 {
1634 curRide->testing_flags &= ~RIDE_TESTING_DROP_UP;
1635 curRide->testing_flags |= RIDE_TESTING_DROP_DOWN;
1636
1637 uint8_t drops = curRide->drops & 0x3F;
1638 if (drops != 0x3F)
1639 drops++;
1640 curRide->drops &= ~0x3F;
1641 curRide->drops |= drops;
1642
1643 curRide->start_drop_height = z / COORDS_Z_STEP;
1644 testingFlags &= ~RIDE_TESTING_DROP_UP;
1645 }
1646
1647 if (testingFlags & RIDE_TESTING_DROP_UP)
1648 {
1649 if (velocity > 0 || !(trackFlags & TRACK_ELEM_FLAG_UP))
1650 {
1651 curRide->testing_flags &= ~RIDE_TESTING_DROP_UP;
1652
1653 int16_t curZ = z / COORDS_Z_STEP - curRide->start_drop_height;
1654 if (curZ < 0)
1655 {
1656 curZ = abs(curZ);
1657 if (curZ > curRide->highest_drop_height)
1658 {
1659 curRide->highest_drop_height = static_cast<uint8_t>(curZ);
1660 }
1661 }
1662 }
1663 }
1664 else if (trackFlags & TRACK_ELEM_FLAG_UP && velocity <= 0)
1665 {
1666 curRide->testing_flags &= ~RIDE_TESTING_DROP_DOWN;
1667 curRide->testing_flags |= RIDE_TESTING_DROP_UP;
1668
1669 uint8_t drops = curRide->drops & 0x3F;
1670 if (drops != 0x3F)
1671 drops++;
1672 curRide->drops &= ~0x3F;
1673 curRide->drops |= drops;
1674
1675 curRide->start_drop_height = z / COORDS_Z_STEP;
1676 }
1677
1678 if (curRide->type == RIDE_TYPE_MINI_GOLF)
1679 {
1680 if (trackFlags & TRACK_ELEM_FLAG_IS_GOLF_HOLE)
1681 {
1682 if (curRide->holes < MAX_GOLF_HOLES)
1683 curRide->holes++;
1684 }
1685 }
1686 else
1687 {
1688 if (trackFlags & TRACK_ELEM_FLAG_NORMAL_TO_INVERSION)
1689 {
1690 if (curRide->inversions < MAX_INVERSIONS)
1691 curRide->inversions++;
1692 }
1693 }
1694
1695 if (trackFlags & TRACK_ELEM_FLAG_HELIX)
1696 {
1697 uint8_t helixes = ride_get_helix_sections(curRide);
1698 if (helixes != MAX_HELICES)
1699 helixes++;
1700
1701 curRide->special_track_elements &= ~0x1F;
1702 curRide->special_track_elements |= helixes;
1703 }
1704 }
1705
1706 if (ride_get_entrance_location(curRide, curRide->current_test_station).IsNull())
1707 return;
1708
1709 if (x == LOCATION_NULL)
1710 {
1711 curRide->testing_flags &= ~RIDE_TESTING_SHELTERED;
1712 return;
1713 }
1714
1715 auto surfaceElement = map_get_surface_element_at(CoordsXY{ x, y });
1716 // If vehicle above ground.
1717 if (surfaceElement != nullptr && surfaceElement->GetBaseZ() <= z)
1718 {
1719 // Set tile_element to first element. Since elements aren't always ordered by base height,
1720 // we must start at the first element and iterate through each tile element.
1721 auto tileElement = map_get_first_element_at(CoordsXY{ x, y });
1722 if (tileElement == nullptr)
1723 return;
1724
1725 bool coverFound = false;
1726 do
1727 {
1728 // If the tile_element is lower than the vehicle, continue (don't set flag)
1729 if (tileElement->GetBaseZ() <= z)
1730 continue;
1731
1732 if (tileElement->GetType() == TILE_ELEMENT_TYPE_LARGE_SCENERY)
1733 {
1734 coverFound = true;
1735 break;
1736 }
1737
1738 if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
1739 {
1740 coverFound = true;
1741 break;
1742 }
1743
1744 if (tileElement->GetType() != TILE_ELEMENT_TYPE_SMALL_SCENERY)
1745 continue;
1746
1747 auto* sceneryEntry = tileElement->AsSmallScenery()->GetEntry();
1748 if (sceneryEntry == nullptr)
1749 continue;
1750
1751 if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE))
1752 {
1753 coverFound = true;
1754 break;
1755 }
1756 // Iterate through each tile_element.
1757 } while (!(tileElement++)->IsLastForTile());
1758
1759 if (!coverFound)
1760 {
1761 curRide->testing_flags &= ~RIDE_TESTING_SHELTERED;
1762 return;
1763 }
1764 }
1765
1766 if (!(curRide->testing_flags & RIDE_TESTING_SHELTERED))
1767 {
1768 curRide->testing_flags |= RIDE_TESTING_SHELTERED;
1769
1770 curRide->IncreaseNumShelteredSections();
1771
1772 if (Pitch != 0)
1773 {
1774 curRide->num_sheltered_sections |= ShelteredSectionsBits::RotatingWhileSheltered;
1775 }
1776
1777 if (bank_rotation != 0)
1778 {
1779 curRide->num_sheltered_sections |= ShelteredSectionsBits::BankingWhileSheltered;
1780 }
1781 }
1782
1783 int32_t distance = ((velocity + acceleration) >> 10) * 42;
1784 if (distance < 0)
1785 return;
1786
1787 curRide->sheltered_length = add_clamp_int32_t(curRide->sheltered_length, distance);
1788 }
1789
1790 struct SoundIdVolume
1791 {
1792 OpenRCT2::Audio::SoundId id;
1793 uint8_t volume;
1794 };
1795
sub_6D7AC0(OpenRCT2::Audio::SoundId currentSoundId,uint8_t currentVolume,OpenRCT2::Audio::SoundId targetSoundId,uint8_t targetVolume)1796 static SoundIdVolume sub_6D7AC0(
1797 OpenRCT2::Audio::SoundId currentSoundId, uint8_t currentVolume, OpenRCT2::Audio::SoundId targetSoundId,
1798 uint8_t targetVolume)
1799 {
1800 if (currentSoundId != OpenRCT2::Audio::SoundId::Null)
1801 {
1802 if (currentSoundId == targetSoundId)
1803 {
1804 currentVolume = std::min<int32_t>(currentVolume + 15, targetVolume);
1805 return { currentSoundId, currentVolume };
1806 }
1807
1808 currentVolume -= 9;
1809 if (currentVolume >= 80)
1810 return { currentSoundId, currentVolume };
1811 }
1812
1813 // Begin sound at quarter volume
1814 currentSoundId = targetSoundId;
1815 currentVolume = targetVolume == 255 ? 255 : targetVolume / 4;
1816
1817 return { currentSoundId, currentVolume };
1818 }
1819
GetLiftHillSound(Ride * curRide,SoundIdVolume & curSound)1820 void Vehicle::GetLiftHillSound(Ride* curRide, SoundIdVolume& curSound)
1821 {
1822 scream_sound_id = OpenRCT2::Audio::SoundId::Null;
1823 if (curRide->type < std::size(RideTypeDescriptors))
1824 {
1825 // Get lift hill sound
1826 curSound.id = GetRideTypeDescriptor(curRide->type).LiftData.sound_id;
1827 curSound.volume = 243;
1828 if (!(sound2_flags & VEHICLE_SOUND2_FLAGS_LIFT_HILL))
1829 curSound.id = OpenRCT2::Audio::SoundId::Null;
1830 }
1831 }
1832
1833 /**
1834 *
1835 * rct2: 0x006D77F2
1836 */
Update()1837 void Vehicle::Update()
1838 {
1839 // The cable lift uses a ride entry index of NULL
1840 if (ride_subtype == OBJECT_ENTRY_INDEX_NULL)
1841 {
1842 CableLiftUpdate();
1843 return;
1844 }
1845
1846 auto rideEntry = GetRideEntry();
1847 if (rideEntry == nullptr)
1848 return;
1849
1850 auto curRide = GetRide();
1851 if (curRide == nullptr)
1852 return;
1853
1854 if (curRide->type >= RIDE_TYPE_COUNT)
1855 return;
1856
1857 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_TESTING))
1858 UpdateMeasurements();
1859
1860 _vehicleBreakdown = 255;
1861 if (curRide->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN))
1862 {
1863 _vehicleBreakdown = curRide->breakdown_reason_pending;
1864 auto vehicleEntry = &rideEntry->vehicles[vehicle_type];
1865 if ((vehicleEntry->flags & VEHICLE_ENTRY_FLAG_POWERED) && curRide->breakdown_reason_pending == BREAKDOWN_SAFETY_CUT_OUT)
1866 {
1867 if (!(vehicleEntry->flags & VEHICLE_ENTRY_FLAG_WATER_RIDE) || (Pitch == 2 && velocity <= 0x20000))
1868 {
1869 SetUpdateFlag(VEHICLE_UPDATE_FLAG_ZERO_VELOCITY);
1870 }
1871 }
1872 }
1873
1874 switch (status)
1875 {
1876 case Vehicle::Status::MovingToEndOfStation:
1877 UpdateMovingToEndOfStation();
1878 break;
1879 case Vehicle::Status::WaitingForPassengers:
1880 UpdateWaitingForPassengers();
1881 break;
1882 case Vehicle::Status::WaitingToDepart:
1883 UpdateWaitingToDepart();
1884 break;
1885 case Vehicle::Status::Crashing:
1886 case Vehicle::Status::Crashed:
1887 UpdateCrash();
1888 break;
1889 case Vehicle::Status::TravellingDodgems:
1890 UpdateDodgemsMode();
1891 break;
1892 case Vehicle::Status::Swinging:
1893 UpdateSwinging();
1894 break;
1895 case Vehicle::Status::SimulatorOperating:
1896 UpdateSimulatorOperating();
1897 break;
1898 case Vehicle::Status::TopSpinOperating:
1899 UpdateTopSpinOperating();
1900 break;
1901 case Vehicle::Status::FerrisWheelRotating:
1902 UpdateFerrisWheelRotating();
1903 break;
1904 case Vehicle::Status::SpaceRingsOperating:
1905 UpdateSpaceRingsOperating();
1906 break;
1907 case Vehicle::Status::HauntedHouseOperating:
1908 UpdateHauntedHouseOperating();
1909 break;
1910 case Vehicle::Status::CrookedHouseOperating:
1911 UpdateCrookedHouseOperating();
1912 break;
1913 case Vehicle::Status::Rotating:
1914 UpdateRotating();
1915 break;
1916 case Vehicle::Status::Departing:
1917 UpdateDeparting();
1918 break;
1919 case Vehicle::Status::Travelling:
1920 UpdateTravelling();
1921 break;
1922 case Vehicle::Status::TravellingCableLift:
1923 UpdateTravellingCableLift();
1924 break;
1925 case Vehicle::Status::TravellingBoat:
1926 UpdateTravellingBoat();
1927 break;
1928 case Vehicle::Status::Arriving:
1929 UpdateArriving();
1930 break;
1931 case Vehicle::Status::UnloadingPassengers:
1932 UpdateUnloadingPassengers();
1933 break;
1934 case Vehicle::Status::WaitingForCableLift:
1935 UpdateWaitingForCableLift();
1936 break;
1937 case Vehicle::Status::ShowingFilm:
1938 UpdateShowingFilm();
1939 break;
1940 case Vehicle::Status::DoingCircusShow:
1941 UpdateDoingCircusShow();
1942 default:
1943 break;
1944 }
1945
1946 UpdateSound();
1947 }
1948
1949 /**
1950 *
1951 * rct2: 0x006D7BCC
1952 */
UpdateMovingToEndOfStation()1953 void Vehicle::UpdateMovingToEndOfStation()
1954 {
1955 auto curRide = GetRide();
1956 if (curRide == nullptr)
1957 return;
1958
1959 int32_t curFlags = 0;
1960 int32_t station = 0;
1961
1962 switch (curRide->mode)
1963 {
1964 case RideMode::UpwardLaunch:
1965 case RideMode::RotatingLift:
1966 case RideMode::DownwardLaunch:
1967 case RideMode::FreefallDrop:
1968 if (velocity >= -131940)
1969 {
1970 acceleration = -3298;
1971 }
1972 if (velocity < -131940)
1973 {
1974 velocity -= velocity / 16;
1975 acceleration = 0;
1976 }
1977 curFlags = UpdateTrackMotion(&station);
1978 if (!(curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_5))
1979 break;
1980 [[fallthrough]];
1981 case RideMode::Dodgems:
1982 case RideMode::Swing:
1983 case RideMode::Rotation:
1984 case RideMode::ForwardRotation:
1985 case RideMode::BackwardRotation:
1986 case RideMode::FilmAvengingAviators:
1987 case RideMode::FilmThrillRiders:
1988 case RideMode::Beginners:
1989 case RideMode::Intense:
1990 case RideMode::Berserk:
1991 case RideMode::MouseTails3DFilm:
1992 case RideMode::StormChasers3DFilm:
1993 case RideMode::SpaceRaiders3DFilm:
1994 case RideMode::SpaceRings:
1995 case RideMode::HauntedHouse:
1996 case RideMode::CrookedHouse:
1997 case RideMode::Circus:
1998 current_station = 0;
1999 velocity = 0;
2000 acceleration = 0;
2001 SetState(Vehicle::Status::WaitingForPassengers);
2002 break;
2003 default:
2004 {
2005 rct_ride_entry* rideEntry = GetRideEntry();
2006 if (rideEntry == nullptr)
2007 {
2008 return;
2009 }
2010
2011 rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle_type];
2012
2013 if (!(vehicleEntry->flags & VEHICLE_ENTRY_FLAG_POWERED))
2014 {
2015 if (velocity <= 131940)
2016 {
2017 acceleration = 3298;
2018 }
2019 }
2020 if (velocity > 131940)
2021 {
2022 velocity -= velocity / 16;
2023 acceleration = 0;
2024 }
2025
2026 curFlags = UpdateTrackMotion(&station);
2027
2028 if (curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_1)
2029 {
2030 velocity = 0;
2031 acceleration = 0;
2032 sub_state++;
2033
2034 if (curRide->mode == RideMode::Race && sub_state >= 40)
2035 {
2036 SetState(Vehicle::Status::WaitingForPassengers);
2037 break;
2038 }
2039 }
2040 else
2041 {
2042 if (velocity > 98955)
2043 {
2044 sub_state = 0;
2045 }
2046 }
2047
2048 if (!(curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_STATION))
2049 break;
2050
2051 current_station = station;
2052 velocity = 0;
2053 acceleration = 0;
2054 SetState(Vehicle::Status::WaitingForPassengers);
2055 break;
2056 }
2057 }
2058 }
2059
2060 /**
2061 *
2062 * rct2: 0x006D7FB4
2063 */
TrainReadyToDepart(uint8_t num_peeps_on_train,uint8_t num_used_seats)2064 void Vehicle::TrainReadyToDepart(uint8_t num_peeps_on_train, uint8_t num_used_seats)
2065 {
2066 if (num_peeps_on_train != num_used_seats)
2067 return;
2068
2069 auto curRide = GetRide();
2070 if (curRide == nullptr)
2071 return;
2072
2073 if (curRide->status == RideStatus::Open && !(curRide->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
2074 && !HasUpdateFlag(VEHICLE_UPDATE_FLAG_TRAIN_READY_DEPART))
2075 {
2076 return;
2077 }
2078
2079 if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN))
2080 {
2081 // Original code did not check if the ride was a boat hire, causing empty boats to leave the platform when closing a
2082 // Boat Hire with passengers on it.
2083 if (curRide->status != RideStatus::Closed || (curRide->num_riders != 0 && curRide->type != RIDE_TYPE_BOAT_HIRE))
2084 {
2085 curRide->stations[current_station].TrainAtStation = RideStation::NO_TRAIN;
2086 sub_state = 2;
2087 return;
2088 }
2089 }
2090
2091 if (curRide->mode == RideMode::ForwardRotation || curRide->mode == RideMode::BackwardRotation)
2092 {
2093 uint8_t seat = ((-Pitch) / 8) & 0xF;
2094 if (peep[seat] != SPRITE_INDEX_NULL)
2095 {
2096 curRide->stations[current_station].TrainAtStation = RideStation::NO_TRAIN;
2097 SetState(Vehicle::Status::UnloadingPassengers);
2098 return;
2099 }
2100
2101 if (num_peeps == 0)
2102 return;
2103
2104 curRide->stations[current_station].TrainAtStation = RideStation::NO_TRAIN;
2105 sub_state = 2;
2106 return;
2107 }
2108
2109 if (num_peeps_on_train == 0)
2110 return;
2111
2112 curRide->stations[current_station].TrainAtStation = RideStation::NO_TRAIN;
2113 SetState(Vehicle::Status::WaitingForPassengers);
2114 }
2115
ride_get_train_index_from_vehicle(Ride * ride,uint16_t spriteIndex)2116 static std::optional<uint32_t> ride_get_train_index_from_vehicle(Ride* ride, uint16_t spriteIndex)
2117 {
2118 uint32_t trainIndex = 0;
2119 while (ride->vehicles[trainIndex] != spriteIndex)
2120 {
2121 trainIndex++;
2122 if (trainIndex >= ride->num_vehicles)
2123 {
2124 // This should really return nullopt, but doing so
2125 // would break some hacked parks that hide track by setting tracked rides'
2126 // track type to, e.g., Crooked House
2127 break;
2128 }
2129 if (trainIndex >= std::size(ride->vehicles))
2130 {
2131 return std::nullopt;
2132 }
2133 }
2134 return { trainIndex };
2135 }
2136
2137 /**
2138 *
2139 * rct2: 0x006D7DA1
2140 */
UpdateWaitingForPassengers()2141 void Vehicle::UpdateWaitingForPassengers()
2142 {
2143 velocity = 0;
2144
2145 auto curRide = GetRide();
2146 if (curRide == nullptr)
2147 return;
2148
2149 if (sub_state == 0)
2150 {
2151 if (!OpenRestraints())
2152 return;
2153
2154 if (ride_get_entrance_location(curRide, current_station).IsNull())
2155 {
2156 curRide->stations[current_station].TrainAtStation = RideStation::NO_TRAIN;
2157 sub_state = 2;
2158 return;
2159 }
2160
2161 auto trainIndex = ride_get_train_index_from_vehicle(curRide, sprite_index);
2162 if (!trainIndex.has_value())
2163 {
2164 return;
2165 }
2166
2167 if (curRide->stations[current_station].TrainAtStation != RideStation::NO_TRAIN)
2168 return;
2169
2170 curRide->stations[current_station].TrainAtStation = trainIndex.value();
2171 sub_state = 1;
2172 time_waiting = 0;
2173
2174 Invalidate();
2175 return;
2176 }
2177 if (sub_state == 1)
2178 {
2179 if (time_waiting != 0xFFFF)
2180 time_waiting++;
2181
2182 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_TRAIN_READY_DEPART);
2183
2184 // 0xF64E31, 0xF64E32, 0xF64E33
2185 uint8_t num_peeps_on_train = 0, num_used_seats_on_train = 0, num_seats_on_train = 0;
2186
2187 for (const Vehicle* trainCar = GetEntity<Vehicle>(sprite_index); trainCar != nullptr;
2188 trainCar = GetEntity<Vehicle>(trainCar->next_vehicle_on_train))
2189 {
2190 num_peeps_on_train += trainCar->num_peeps;
2191 num_used_seats_on_train += trainCar->next_free_seat;
2192 num_seats_on_train += trainCar->num_seats;
2193 }
2194
2195 num_seats_on_train &= 0x7F;
2196
2197 if (curRide->SupportsStatus(RideStatus::Testing))
2198 {
2199 if (time_waiting < 20)
2200 {
2201 TrainReadyToDepart(num_peeps_on_train, num_used_seats_on_train);
2202 return;
2203 }
2204 }
2205 else
2206 {
2207 if (num_peeps_on_train == 0)
2208 {
2209 TrainReadyToDepart(num_peeps_on_train, num_used_seats_on_train);
2210 return;
2211 }
2212 }
2213
2214 if (curRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LOAD_OPTIONS))
2215 {
2216 if (curRide->depart_flags & RIDE_DEPART_WAIT_FOR_MINIMUM_LENGTH)
2217 {
2218 if (curRide->min_waiting_time * 32 > time_waiting)
2219 {
2220 TrainReadyToDepart(num_peeps_on_train, num_used_seats_on_train);
2221 return;
2222 }
2223 }
2224 if (curRide->depart_flags & RIDE_DEPART_WAIT_FOR_MAXIMUM_LENGTH)
2225 {
2226 if (curRide->max_waiting_time * 32 < time_waiting)
2227 {
2228 SetUpdateFlag(VEHICLE_UPDATE_FLAG_TRAIN_READY_DEPART);
2229 TrainReadyToDepart(num_peeps_on_train, num_used_seats_on_train);
2230 return;
2231 }
2232 }
2233 }
2234
2235 if (curRide->depart_flags & RIDE_DEPART_LEAVE_WHEN_ANOTHER_ARRIVES)
2236 {
2237 for (auto train_id : curRide->vehicles)
2238 {
2239 if (train_id == sprite_index)
2240 continue;
2241
2242 Vehicle* train = GetEntity<Vehicle>(train_id);
2243 if (train == nullptr)
2244 continue;
2245
2246 if (train->status == Vehicle::Status::UnloadingPassengers
2247 || train->status == Vehicle::Status::MovingToEndOfStation)
2248 {
2249 if (train->current_station == current_station)
2250 {
2251 SetUpdateFlag(VEHICLE_UPDATE_FLAG_TRAIN_READY_DEPART);
2252 TrainReadyToDepart(num_peeps_on_train, num_used_seats_on_train);
2253 return;
2254 }
2255 }
2256 }
2257 }
2258
2259 if (curRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LOAD_OPTIONS)
2260 && curRide->depart_flags & RIDE_DEPART_WAIT_FOR_LOAD)
2261 {
2262 if (num_peeps_on_train == num_seats_on_train)
2263 {
2264 SetUpdateFlag(VEHICLE_UPDATE_FLAG_TRAIN_READY_DEPART);
2265 TrainReadyToDepart(num_peeps_on_train, num_used_seats_on_train);
2266 return;
2267 }
2268
2269 // any load: load=4 , full: load=3 , 3/4s: load=2 , half: load=1 , quarter: load=0
2270 uint8_t load = curRide->depart_flags & RIDE_DEPART_WAIT_FOR_LOAD_MASK;
2271
2272 // We want to wait for ceiling((load+1)/4 * num_seats_on_train) peeps, the +3 below is used instead of
2273 // ceil() to prevent issues on different cpus/platforms with floats. Note that vanilla RCT1/2 rounded
2274 // down here; our change reflects the expected behaviour for waiting for a minimum load target (see #9987)
2275 uint8_t peepTarget = ((load + 1) * num_seats_on_train + 3) / 4;
2276
2277 if (load == 4) // take care of "any load" special case
2278 peepTarget = 1;
2279
2280 if (num_peeps_on_train >= peepTarget)
2281 SetUpdateFlag(VEHICLE_UPDATE_FLAG_TRAIN_READY_DEPART);
2282
2283 TrainReadyToDepart(num_peeps_on_train, num_used_seats_on_train);
2284 return;
2285 }
2286
2287 SetUpdateFlag(VEHICLE_UPDATE_FLAG_TRAIN_READY_DEPART);
2288 TrainReadyToDepart(num_peeps_on_train, num_used_seats_on_train);
2289 return;
2290 }
2291
2292 if (!CloseRestraints())
2293 return;
2294
2295 velocity = 0;
2296 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_WAIT_ON_ADJACENT);
2297
2298 if (curRide->depart_flags & RIDE_DEPART_SYNCHRONISE_WITH_ADJACENT_STATIONS)
2299 {
2300 SetUpdateFlag(VEHICLE_UPDATE_FLAG_WAIT_ON_ADJACENT);
2301 }
2302
2303 SetState(Vehicle::Status::WaitingToDepart);
2304 }
2305
2306 /**
2307 *
2308 * rct2: 0x006D91BF
2309 */
UpdateDodgemsMode()2310 void Vehicle::UpdateDodgemsMode()
2311 {
2312 auto curRide = GetRide();
2313 if (curRide == nullptr)
2314 return;
2315
2316 rct_ride_entry* rideEntry = GetRideEntry();
2317 if (rideEntry == nullptr)
2318 {
2319 return;
2320 }
2321 rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle_type];
2322
2323 // Mark the dodgem as in use.
2324 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_DODGEM_INUSE_LIGHTS && animation_frame != 1)
2325 {
2326 animation_frame = 1;
2327 Invalidate();
2328 }
2329
2330 UpdateMotionDodgems();
2331
2332 // Update the length of time vehicle has been in dodgems mode
2333 if (sub_state++ == 0xFF)
2334 {
2335 var_CE++;
2336 }
2337
2338 if (curRide->lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING)
2339 return;
2340
2341 // Mark the dodgem as not in use.
2342 animation_frame = 0;
2343 Invalidate();
2344 velocity = 0;
2345 acceleration = 0;
2346 SetState(Vehicle::Status::UnloadingPassengers);
2347 }
2348
2349 /**
2350 *
2351 * rct2: 0x006D80BE
2352 */
UpdateWaitingToDepart()2353 void Vehicle::UpdateWaitingToDepart()
2354 {
2355 auto curRide = GetRide();
2356 if (curRide == nullptr)
2357 return;
2358
2359 bool shouldBreak = false;
2360 if (curRide->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
2361 {
2362 switch (curRide->breakdown_reason_pending)
2363 {
2364 case BREAKDOWN_RESTRAINTS_STUCK_CLOSED:
2365 case BREAKDOWN_RESTRAINTS_STUCK_OPEN:
2366 case BREAKDOWN_DOORS_STUCK_CLOSED:
2367 case BREAKDOWN_DOORS_STUCK_OPEN:
2368 break;
2369 default:
2370 shouldBreak = true;
2371 break;
2372 }
2373 }
2374
2375 bool skipCheck = false;
2376 if (shouldBreak || curRide->status != RideStatus::Open)
2377 {
2378 if (curRide->mode == RideMode::ForwardRotation || curRide->mode == RideMode::BackwardRotation)
2379 {
2380 uint8_t seat = ((-Pitch) >> 3) & 0xF;
2381 if (peep[seat * 2] == SPRITE_INDEX_NULL)
2382 {
2383 if (num_peeps == 0)
2384 {
2385 skipCheck = true;
2386 }
2387 }
2388 else
2389 {
2390 if (!ride_get_exit_location(curRide, current_station).IsNull())
2391 {
2392 SetState(Vehicle::Status::UnloadingPassengers);
2393 return;
2394 }
2395 }
2396 }
2397 else
2398 {
2399 for (const Vehicle* trainCar = GetEntity<Vehicle>(sprite_index); trainCar != nullptr;
2400 trainCar = GetEntity<Vehicle>(trainCar->next_vehicle_on_train))
2401 {
2402 if (trainCar->num_peeps != 0)
2403 {
2404 if (!ride_get_exit_location(curRide, current_station).IsNull())
2405 {
2406 SetState(Vehicle::Status::UnloadingPassengers);
2407 return;
2408 }
2409 break;
2410 }
2411 }
2412 }
2413 }
2414
2415 if (!skipCheck)
2416 {
2417 if (!(curRide->stations[current_station].Depart & STATION_DEPART_FLAG))
2418 return;
2419 }
2420
2421 if (curRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_CAN_SYNCHRONISE_ADJACENT_STATIONS))
2422 {
2423 if (curRide->depart_flags & RIDE_DEPART_SYNCHRONISE_WITH_ADJACENT_STATIONS)
2424 {
2425 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_WAIT_ON_ADJACENT))
2426 {
2427 if (!CanDepartSynchronised())
2428 {
2429 return;
2430 }
2431 }
2432 }
2433 }
2434
2435 SetState(Vehicle::Status::Departing);
2436
2437 if (curRide->lifecycle_flags & RIDE_LIFECYCLE_CABLE_LIFT)
2438 {
2439 CoordsXYE track;
2440 int32_t zUnused;
2441 int32_t direction;
2442
2443 uint8_t trackDirection = GetTrackDirection();
2444 if (track_block_get_next_from_zero(TrackLocation, curRide, trackDirection, &track, &zUnused, &direction, false))
2445 {
2446 if (track.element->AsTrack()->HasCableLift())
2447 {
2448 SetState(Vehicle::Status::WaitingForCableLift, sub_state);
2449 }
2450 }
2451 }
2452
2453 switch (curRide->mode)
2454 {
2455 case RideMode::Dodgems:
2456 // Dodgems mode uses sub_state / var_CE to tell how long
2457 // the vehicle has been ridden.
2458 SetState(Vehicle::Status::TravellingDodgems);
2459 var_CE = 0;
2460 UpdateDodgemsMode();
2461 break;
2462 case RideMode::Swing:
2463 SetState(Vehicle::Status::Swinging);
2464 var_CE = 0;
2465 current_time = -1;
2466 UpdateSwinging();
2467 break;
2468 case RideMode::Rotation:
2469 SetState(Vehicle::Status::Rotating);
2470 var_CE = 0;
2471 current_time = -1;
2472 UpdateRotating();
2473 break;
2474 case RideMode::FilmAvengingAviators:
2475 SetState(Vehicle::Status::SimulatorOperating);
2476 current_time = -1;
2477 UpdateSimulatorOperating();
2478 break;
2479 case RideMode::FilmThrillRiders:
2480 SetState(Vehicle::Status::SimulatorOperating, 1);
2481 current_time = -1;
2482 UpdateSimulatorOperating();
2483 break;
2484 case RideMode::Beginners:
2485 case RideMode::Intense:
2486 case RideMode::Berserk:
2487 SetState(Vehicle::Status::TopSpinOperating, sub_state);
2488 switch (curRide->mode)
2489 {
2490 case RideMode::Beginners:
2491 sub_state = 0;
2492 break;
2493 case RideMode::Intense:
2494 sub_state = 1;
2495 break;
2496 case RideMode::Berserk:
2497 sub_state = 2;
2498 break;
2499 default:
2500 {
2501 // This is workaround for multiple compilation errors of type "enumeration value ‘RIDE_MODE_*' not handled
2502 // in switch [-Werror=switch]"
2503 }
2504 }
2505 current_time = -1;
2506 Pitch = 0;
2507 bank_rotation = 0;
2508 UpdateTopSpinOperating();
2509 break;
2510 case RideMode::ForwardRotation:
2511 case RideMode::BackwardRotation:
2512 SetState(Vehicle::Status::FerrisWheelRotating, Pitch);
2513 var_CE = 0;
2514 ferris_wheel_var_0 = 8;
2515 ferris_wheel_var_1 = 8;
2516 UpdateFerrisWheelRotating();
2517 break;
2518 case RideMode::MouseTails3DFilm:
2519 case RideMode::StormChasers3DFilm:
2520 case RideMode::SpaceRaiders3DFilm:
2521 SetState(Vehicle::Status::ShowingFilm, sub_state);
2522 switch (curRide->mode)
2523 {
2524 case RideMode::MouseTails3DFilm:
2525 sub_state = 0;
2526 break;
2527 case RideMode::StormChasers3DFilm:
2528 sub_state = 1;
2529 break;
2530 case RideMode::SpaceRaiders3DFilm:
2531 sub_state = 2;
2532 break;
2533 default:
2534 {
2535 // This is workaround for multiple compilation errors of type "enumeration value ‘RIDE_MODE_*' not handled
2536 // in switch [-Werror=switch]"
2537 }
2538 }
2539 current_time = -1;
2540 UpdateShowingFilm();
2541 break;
2542 case RideMode::Circus:
2543 SetState(Vehicle::Status::DoingCircusShow);
2544 current_time = -1;
2545 UpdateDoingCircusShow();
2546 break;
2547 case RideMode::SpaceRings:
2548 SetState(Vehicle::Status::SpaceRingsOperating);
2549 Pitch = 0;
2550 current_time = -1;
2551 UpdateSpaceRingsOperating();
2552 break;
2553 case RideMode::HauntedHouse:
2554 SetState(Vehicle::Status::HauntedHouseOperating);
2555 Pitch = 0;
2556 current_time = -1;
2557 UpdateHauntedHouseOperating();
2558 break;
2559 case RideMode::CrookedHouse:
2560 SetState(Vehicle::Status::CrookedHouseOperating);
2561 Pitch = 0;
2562 current_time = -1;
2563 UpdateCrookedHouseOperating();
2564 break;
2565 default:
2566 SetState(status);
2567 var_CE = 0;
2568 break;
2569 }
2570 }
2571
2572 struct rct_synchronised_vehicle
2573 {
2574 ride_id_t ride_id;
2575 StationIndex stationIndex;
2576 uint16_t vehicle_id;
2577 };
2578
2579 constexpr int32_t SYNCHRONISED_VEHICLE_COUNT = 16;
2580
2581 // Synchronised vehicle info
2582 static rct_synchronised_vehicle _synchronisedVehicles[SYNCHRONISED_VEHICLE_COUNT] = {};
2583
2584 static rct_synchronised_vehicle* _lastSynchronisedVehicle = nullptr;
2585
2586 /**
2587 * Checks if a map position contains a synchronised ride station and adds the vehicle
2588 * to synchronise to the vehicle synchronisation list.
2589 * rct2: 0x006DE1A4
2590 */
try_add_synchronised_station(const CoordsXYZ & coords)2591 static bool try_add_synchronised_station(const CoordsXYZ& coords)
2592 {
2593 // make sure we are in map bounds
2594 if (!map_is_location_valid(coords))
2595 {
2596 return false;
2597 }
2598
2599 TileElement* tileElement = get_station_platform({ coords, coords.z + 2 * COORDS_Z_STEP });
2600 if (tileElement == nullptr)
2601 {
2602 /* No station platform element found,
2603 * so no station to synchronise */
2604 return false;
2605 }
2606
2607 auto rideIndex = tileElement->AsTrack()->GetRideIndex();
2608 auto ride = get_ride(rideIndex);
2609 if (ride == nullptr || !(ride->depart_flags & RIDE_DEPART_SYNCHRONISE_WITH_ADJACENT_STATIONS))
2610 {
2611 /* Ride is not set to synchronise with adjacent stations. */
2612 return false;
2613 }
2614
2615 /* From this point on, the ride of the map element is one that is set
2616 * to sync with adjacent stations, so it will return true.
2617 * Still to determine if a vehicle to sync can be identified. */
2618
2619 auto stationIndex = tileElement->AsTrack()->GetStationIndex();
2620
2621 rct_synchronised_vehicle* sv = _lastSynchronisedVehicle;
2622 sv->ride_id = rideIndex;
2623 sv->stationIndex = stationIndex;
2624 sv->vehicle_id = SPRITE_INDEX_NULL;
2625 _lastSynchronisedVehicle++;
2626
2627 /* Ride vehicles are not on the track (e.g. ride is/was under
2628 * construction), so just return; vehicle_id for this station
2629 * is SPRITE_INDEX_NULL. */
2630 if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK))
2631 {
2632 return true;
2633 }
2634
2635 /* Station is not ready to depart, so just return;
2636 * vehicle_id for this station is SPRITE_INDEX_NULL. */
2637 if (!(ride->stations[stationIndex].Depart & STATION_DEPART_FLAG))
2638 {
2639 return true;
2640 }
2641
2642 // Look for a vehicle on this station waiting to depart.
2643 for (int32_t i = 0; i < ride->num_vehicles; i++)
2644 {
2645 auto* vehicle = GetEntity<Vehicle>(ride->vehicles[i]);
2646 if (vehicle == nullptr)
2647 {
2648 continue;
2649 }
2650
2651 if (vehicle->status != Vehicle::Status::WaitingToDepart)
2652 {
2653 continue;
2654 }
2655 if (vehicle->sub_state != 0)
2656 {
2657 continue;
2658 }
2659 if (!vehicle->HasUpdateFlag(VEHICLE_UPDATE_FLAG_WAIT_ON_ADJACENT))
2660 {
2661 continue;
2662 }
2663 if (vehicle->current_station != stationIndex)
2664 {
2665 continue;
2666 }
2667
2668 sv->vehicle_id = vehicle->sprite_index;
2669 return true;
2670 }
2671
2672 /* No vehicle found waiting to depart (with sync adjacent) at the
2673 * station, so just return; vehicle_id for this station is
2674 * SPRITE_INDEX_NULL. */
2675 return true;
2676 }
2677
2678 /**
2679 * Checks whether a vehicle can depart a station when set to synchronise with adjacent stations.
2680 * rct2: 0x006DE287
2681 * @param vehicle The vehicle waiting to depart.
2682 * @returns true if the vehicle can depart (all adjacent trains are ready or broken down), otherwise false.
2683 *
2684 * Permits vehicles to depart in two ways:
2685 * Returns true, permitting the vehicle in the param to depart immediately;
2686 * The vehicle flag VEHICLE_UPDATE_FLAG_WAIT_ON_ADJACENT is cleared for those
2687 * vehicles that depart in sync with the vehicle in the param.
2688 */
ride_station_can_depart_synchronised(const Ride & ride,StationIndex station)2689 static bool ride_station_can_depart_synchronised(const Ride& ride, StationIndex station)
2690 {
2691 auto location = ride.stations[station].GetStart();
2692
2693 auto tileElement = map_get_track_element_at(location);
2694 if (tileElement == nullptr)
2695 {
2696 return false;
2697 }
2698
2699 // Reset the list of synchronised vehicles to empty.
2700 _lastSynchronisedVehicle = _synchronisedVehicles;
2701
2702 /* Search for stations to sync in both directions from the current tile.
2703 * We allow for some space between stations, and every time a station
2704 * is found we allow for space between that and the next.
2705 */
2706
2707 int32_t direction = tileElement->GetDirectionWithOffset(1);
2708 int32_t maxCheckDistance = RIDE_ADJACENCY_CHECK_DISTANCE;
2709 int32_t spaceBetween = maxCheckDistance;
2710
2711 while (_lastSynchronisedVehicle < &_synchronisedVehicles[SYNCHRONISED_VEHICLE_COUNT - 1])
2712 {
2713 location += CoordsXYZ{ CoordsDirectionDelta[direction], 0 };
2714 if (try_add_synchronised_station(location))
2715 {
2716 spaceBetween = maxCheckDistance;
2717 continue;
2718 }
2719 if (spaceBetween-- == 0)
2720 {
2721 break;
2722 }
2723 }
2724
2725 // Other search direction.
2726 location = ride.stations[station].GetStart();
2727 direction = direction_reverse(direction) & 3;
2728 spaceBetween = maxCheckDistance;
2729 while (_lastSynchronisedVehicle < &_synchronisedVehicles[SYNCHRONISED_VEHICLE_COUNT - 1])
2730 {
2731 location += CoordsXYZ{ CoordsDirectionDelta[direction], 0 };
2732 if (try_add_synchronised_station(location))
2733 {
2734 spaceBetween = maxCheckDistance;
2735 continue;
2736 }
2737 if (spaceBetween-- == 0)
2738 {
2739 break;
2740 }
2741 }
2742
2743 if (_lastSynchronisedVehicle == _synchronisedVehicles)
2744 {
2745 // No adjacent stations, allow depart
2746 return true;
2747 }
2748
2749 for (rct_synchronised_vehicle* sv = _synchronisedVehicles; sv < _lastSynchronisedVehicle; sv++)
2750 {
2751 Ride* sv_ride = get_ride(sv->ride_id);
2752
2753 if (!(sv_ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN))
2754 {
2755 if (sv_ride->status != RideStatus::Closed)
2756 {
2757 if (sv_ride->IsBlockSectioned())
2758 {
2759 if (!(sv_ride->stations[sv->stationIndex].Depart & STATION_DEPART_FLAG))
2760 {
2761 sv = _synchronisedVehicles;
2762 ride_id_t rideId = RIDE_ID_NULL;
2763 for (; sv < _lastSynchronisedVehicle; sv++)
2764 {
2765 if (rideId == RIDE_ID_NULL)
2766 {
2767 rideId = sv->ride_id;
2768 }
2769 if (rideId != sv->ride_id)
2770 {
2771 // Here the sync-ed stations are not all from the same ride.
2772 return false;
2773 }
2774 }
2775
2776 /* Here all the of sync-ed stations are from the same ride */
2777 auto curRide = get_ride(rideId);
2778 if (curRide != nullptr)
2779 {
2780 for (int32_t i = 0; i < curRide->num_vehicles; i++)
2781 {
2782 Vehicle* v = GetEntity<Vehicle>(curRide->vehicles[i]);
2783 if (v == nullptr)
2784 {
2785 continue;
2786 }
2787 if (v->status != Vehicle::Status::WaitingToDepart && v->velocity != 0)
2788 {
2789 // Here at least one vehicle on the ride is moving.
2790 return false;
2791 }
2792 }
2793 }
2794
2795 // UNCERTAIN: is the return desired here, or rather continue on with the general checks?
2796 return true;
2797 }
2798 }
2799 // There is no vehicle waiting at this station to sync with.
2800 if (sv->vehicle_id == SPRITE_INDEX_NULL)
2801 {
2802 // Check conditions for departing without all stations being in sync.
2803 if (_lastSynchronisedVehicle > &_synchronisedVehicles[1])
2804 {
2805 // Sync condition: there are at least 3 stations to sync
2806 return false;
2807 }
2808 ride_id_t someRideIndex = _synchronisedVehicles[0].ride_id;
2809 if (someRideIndex != ride.id)
2810 {
2811 // Sync condition: the first station to sync is a different ride
2812 return false;
2813 }
2814
2815 int32_t numTrainsAtStation = 0;
2816 int32_t numTravelingTrains = 0;
2817 auto currentStation = sv->stationIndex;
2818 for (int32_t i = 0; i < sv_ride->num_vehicles; i++)
2819 {
2820 auto* otherVehicle = GetEntity<Vehicle>(sv_ride->vehicles[i]);
2821 if (otherVehicle == nullptr)
2822 {
2823 continue;
2824 }
2825 if (otherVehicle->status != Vehicle::Status::Travelling)
2826 {
2827 if (currentStation == otherVehicle->current_station)
2828 {
2829 if (otherVehicle->status == Vehicle::Status::WaitingToDepart
2830 || otherVehicle->status == Vehicle::Status::MovingToEndOfStation)
2831 {
2832 numTrainsAtStation++;
2833 }
2834 }
2835 }
2836 else
2837 {
2838 numTravelingTrains++;
2839 }
2840 }
2841
2842 int32_t totalTrains = numTrainsAtStation + numTravelingTrains;
2843 if (totalTrains != sv_ride->num_vehicles || numTravelingTrains >= sv_ride->num_vehicles / 2)
2844 {
2845 // if (numArrivingTrains > 0 || numTravelingTrains >= sv_ride->num_vehicles / 2) {
2846 /* Sync condition: If there are trains arriving at the
2847 * station or half or more of the ride trains are
2848 * travelling, this station must be sync-ed before the
2849 * trains can depart! */
2850 return false;
2851 }
2852
2853 /* Sync exception - train is not arriving at the station
2854 * and there are less than half the trains for the ride
2855 * travelling. */
2856 continue;
2857 }
2858 }
2859 }
2860 }
2861
2862 // At this point all vehicles in _snychronisedVehicles can depart.
2863 for (rct_synchronised_vehicle* sv = _synchronisedVehicles; sv < _lastSynchronisedVehicle; sv++)
2864 {
2865 auto v = GetEntity<Vehicle>(sv->vehicle_id);
2866 if (v != nullptr)
2867 {
2868 v->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_WAIT_ON_ADJACENT);
2869 }
2870 }
2871
2872 return true;
2873 }
2874
CanDepartSynchronised() const2875 bool Vehicle::CanDepartSynchronised() const
2876 {
2877 auto curRide = GetRide();
2878 if (curRide == nullptr)
2879 return false;
2880 return ride_station_can_depart_synchronised(*curRide, current_station);
2881 }
2882
2883 /**
2884 *
2885 * rct2: 0x006D9EB0
2886 */
PeepEasterEggHereWeAre() const2887 void Vehicle::PeepEasterEggHereWeAre() const
2888 {
2889 for (Vehicle* vehicle = GetEntity<Vehicle>(sprite_index); vehicle != nullptr;
2890 vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
2891 {
2892 for (int32_t i = 0; i < vehicle->num_peeps; ++i)
2893 {
2894 auto* curPeep = GetEntity<Guest>(vehicle->peep[i]);
2895 if (curPeep != nullptr && curPeep->PeepFlags & PEEP_FLAGS_HERE_WE_ARE)
2896 {
2897 curPeep->InsertNewThought(PeepThoughtType::HereWeAre, curPeep->CurrentRide);
2898 }
2899 }
2900 }
2901 }
2902
2903 /**
2904 * Performed when vehicle has completed a full circuit
2905 * rct2: 0x006D7338
2906 */
test_finish(Ride & ride)2907 static void test_finish(Ride& ride)
2908 {
2909 ride.lifecycle_flags &= ~RIDE_LIFECYCLE_TEST_IN_PROGRESS;
2910 ride.lifecycle_flags |= RIDE_LIFECYCLE_TESTED;
2911
2912 for (int32_t i = ride.num_stations - 1; i >= 1; i--)
2913 {
2914 if (ride.stations[i - 1].SegmentTime != 0)
2915 continue;
2916
2917 uint16_t oldTime = ride.stations[i - 1].SegmentTime;
2918 ride.stations[i - 1].SegmentTime = ride.stations[i].SegmentTime;
2919 ride.stations[i].SegmentTime = oldTime;
2920
2921 int32_t oldLength = ride.stations[i - 1].SegmentLength;
2922 ride.stations[i - 1].SegmentLength = ride.stations[i].SegmentLength;
2923 ride.stations[i].SegmentLength = oldLength;
2924 }
2925
2926 uint32_t totalTime = 0;
2927 for (uint8_t i = 0; i < ride.num_stations; ++i)
2928 {
2929 totalTime += ride.stations[i].SegmentTime;
2930 }
2931
2932 totalTime = std::max(totalTime, 1u);
2933 ride.average_speed = ride.average_speed / totalTime;
2934 window_invalidate_by_number(WC_RIDE, EnumValue(ride.id));
2935 }
2936
UpdateTestFinish()2937 void Vehicle::UpdateTestFinish()
2938 {
2939 auto curRide = GetRide();
2940 if (curRide == nullptr)
2941 return;
2942 test_finish(*curRide);
2943 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_TESTING);
2944 }
2945
2946 /**
2947 *
2948 * rct2: 0x006D6BE7
2949 */
test_reset(Ride & ride,StationIndex curStation)2950 static void test_reset(Ride& ride, StationIndex curStation)
2951 {
2952 ride.lifecycle_flags |= RIDE_LIFECYCLE_TEST_IN_PROGRESS;
2953 ride.lifecycle_flags &= ~RIDE_LIFECYCLE_NO_RAW_STATS;
2954 ride.max_speed = 0;
2955 ride.average_speed = 0;
2956 ride.current_test_segment = 0;
2957 ride.average_speed_test_timeout = 0;
2958 ride.max_positive_vertical_g = FIXED_2DP(1, 0);
2959 ride.max_negative_vertical_g = FIXED_2DP(1, 0);
2960 ride.max_lateral_g = 0;
2961 ride.previous_vertical_g = 0;
2962 ride.previous_lateral_g = 0;
2963 ride.testing_flags = 0;
2964 ride.CurTestTrackLocation.SetNull();
2965 ride.turn_count_default = 0;
2966 ride.turn_count_banked = 0;
2967 ride.turn_count_sloped = 0;
2968 ride.inversions = 0;
2969 ride.holes = 0;
2970 ride.sheltered_eighths = 0;
2971 ride.drops = 0;
2972 ride.sheltered_length = 0;
2973 ride.var_11C = 0;
2974 ride.num_sheltered_sections = 0;
2975 ride.highest_drop_height = 0;
2976 ride.special_track_elements = 0;
2977 for (auto& station : ride.stations)
2978 {
2979 station.SegmentLength = 0;
2980 station.SegmentTime = 0;
2981 }
2982 ride.total_air_time = 0;
2983 ride.current_test_station = curStation;
2984 window_invalidate_by_number(WC_RIDE, EnumValue(ride.id));
2985 }
2986
TestReset()2987 void Vehicle::TestReset()
2988 {
2989 SetUpdateFlag(VEHICLE_UPDATE_FLAG_TESTING);
2990 auto curRide = GetRide();
2991 if (curRide == nullptr)
2992 return;
2993 test_reset(*curRide, current_station);
2994 }
2995
CurrentTowerElementIsTop()2996 bool Vehicle::CurrentTowerElementIsTop()
2997 {
2998 TileElement* tileElement = map_get_track_element_at_of_type(TrackLocation, GetTrackType());
2999 if (tileElement != nullptr)
3000 {
3001 while (!tileElement->IsLastForTile())
3002 {
3003 tileElement++;
3004 if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK
3005 && tileElement->AsTrack()->GetTrackType() == TrackElemType::TowerSection)
3006 {
3007 return false;
3008 }
3009 }
3010 }
3011 return true;
3012 }
3013
3014 /**
3015 *
3016 * rct2: 0x006D986C
3017 */
UpdateTravellingBoatHireSetup()3018 void Vehicle::UpdateTravellingBoatHireSetup()
3019 {
3020 var_34 = sprite_direction;
3021 TrackLocation.x = x;
3022 TrackLocation.y = y;
3023 TrackLocation = TrackLocation.ToTileStart();
3024
3025 CoordsXY location = CoordsXY(TrackLocation) + CoordsDirectionDelta[sprite_direction >> 3];
3026
3027 BoatLocation = location;
3028 var_35 = 0;
3029 // No longer on a track so reset to 0 for import/export
3030 SetTrackDirection(0);
3031 SetTrackType(0);
3032 SetState(Vehicle::Status::TravellingBoat);
3033 remaining_distance += 27924;
3034
3035 UpdateTravellingBoat();
3036 }
3037
3038 /**
3039 *
3040 * rct2: 0x006D982F
3041 */
UpdateDepartingBoatHire()3042 void Vehicle::UpdateDepartingBoatHire()
3043 {
3044 lost_time_out = 0;
3045
3046 auto curRide = GetRide();
3047 if (curRide == nullptr)
3048 return;
3049
3050 curRide->stations[current_station].Depart &= STATION_DEPART_FLAG;
3051 uint8_t waitingTime = std::max(curRide->min_waiting_time, static_cast<uint8_t>(3));
3052 waitingTime = std::min(waitingTime, static_cast<uint8_t>(127));
3053 curRide->stations[current_station].Depart |= waitingTime;
3054 UpdateTravellingBoatHireSetup();
3055 }
3056
3057 /**
3058 *
3059 * rct2: 0x006D845B
3060 */
UpdateDeparting()3061 void Vehicle::UpdateDeparting()
3062 {
3063 auto curRide = GetRide();
3064 if (curRide == nullptr)
3065 return;
3066
3067 auto rideEntry = GetRideEntry();
3068 if (rideEntry == nullptr)
3069 return;
3070
3071 if (sub_state == 0)
3072 {
3073 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_TRAIN))
3074 {
3075 if (curRide->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
3076 return;
3077
3078 curRide->lifecycle_flags |= RIDE_LIFECYCLE_BROKEN_DOWN;
3079 ride_breakdown_add_news_item(curRide);
3080
3081 curRide->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST
3082 | RIDE_INVALIDATE_RIDE_MAINTENANCE;
3083 curRide->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
3084 curRide->inspection_station = current_station;
3085 curRide->breakdown_reason = curRide->breakdown_reason_pending;
3086 velocity = 0;
3087 return;
3088 }
3089
3090 sub_state = 1;
3091 PeepEasterEggHereWeAre();
3092
3093 if (rideEntry->flags & RIDE_ENTRY_FLAG_PLAY_DEPART_SOUND)
3094 {
3095 auto soundId = (rideEntry->vehicles[0].sound_range == 4) ? OpenRCT2::Audio::SoundId::Tram
3096 : OpenRCT2::Audio::SoundId::TrainDeparting;
3097
3098 OpenRCT2::Audio::Play3D(soundId, GetLocation());
3099 }
3100
3101 if (curRide->mode == RideMode::UpwardLaunch || (curRide->mode == RideMode::DownwardLaunch && var_CE > 1))
3102 {
3103 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::RideLaunch2, GetLocation());
3104 }
3105
3106 if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_TESTED))
3107 {
3108 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_TESTING))
3109 {
3110 if (curRide->current_test_segment + 1 < curRide->num_stations)
3111 {
3112 curRide->current_test_segment++;
3113 curRide->current_test_station = current_station;
3114 }
3115 else
3116 {
3117 UpdateTestFinish();
3118 }
3119 }
3120 else if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_TEST_IN_PROGRESS) && !IsGhost())
3121 {
3122 TestReset();
3123 }
3124 }
3125 }
3126
3127 rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle_type];
3128
3129 switch (curRide->mode)
3130 {
3131 case RideMode::ReverseInclineLaunchedShuttle:
3132 if (velocity >= -131940)
3133 acceleration = -3298;
3134 break;
3135 case RideMode::PoweredLaunchPasstrough:
3136 case RideMode::PoweredLaunch:
3137 case RideMode::PoweredLaunchBlockSectioned:
3138 case RideMode::LimPoweredLaunch:
3139 case RideMode::UpwardLaunch:
3140 if (curRide->type == RIDE_TYPE_AIR_POWERED_VERTICAL_COASTER)
3141 {
3142 if ((curRide->launch_speed << 16) > velocity)
3143 {
3144 acceleration = curRide->launch_speed << 13;
3145 }
3146 break;
3147 }
3148
3149 if ((curRide->launch_speed << 16) > velocity)
3150 acceleration = curRide->launch_speed << 12;
3151 break;
3152 case RideMode::DownwardLaunch:
3153 if (var_CE >= 1)
3154 {
3155 if ((14 << 16) > velocity)
3156 acceleration = 14 << 12;
3157 break;
3158 }
3159 [[fallthrough]];
3160 case RideMode::ContinuousCircuit:
3161 case RideMode::ContinuousCircuitBlockSectioned:
3162 case RideMode::RotatingLift:
3163 case RideMode::FreefallDrop:
3164 case RideMode::BoatHire:
3165 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_POWERED)
3166 break;
3167
3168 if (velocity <= 131940)
3169 acceleration = 3298;
3170 break;
3171 default:
3172 {
3173 // This is workaround for multiple compilation errors of type "enumeration value ‘RIDE_MODE_*' not handled
3174 // in switch [-Werror=switch]"
3175 }
3176 }
3177
3178 uint32_t curFlags = UpdateTrackMotion(nullptr);
3179
3180 if (curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_8)
3181 {
3182 if (curRide->mode == RideMode::ReverseInclineLaunchedShuttle)
3183 {
3184 velocity = -velocity;
3185 FinishDeparting();
3186 return;
3187 }
3188 }
3189
3190 if (curFlags & (VEHICLE_UPDATE_MOTION_TRACK_FLAG_5 | VEHICLE_UPDATE_MOTION_TRACK_FLAG_12))
3191 {
3192 if (curRide->mode == RideMode::BoatHire)
3193 {
3194 UpdateDepartingBoatHire();
3195 return;
3196 }
3197 if (curRide->mode == RideMode::ReverseInclineLaunchedShuttle)
3198 {
3199 velocity = -velocity;
3200 FinishDeparting();
3201 return;
3202 }
3203 if (curRide->mode == RideMode::Shuttle)
3204 {
3205 update_flags ^= VEHICLE_UPDATE_FLAG_REVERSING_SHUTTLE;
3206 velocity = 0;
3207
3208 // We have turned, so treat it like entering a new tile
3209 UpdateCrossings();
3210 }
3211 }
3212
3213 if (curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_ON_LIFT_HILL)
3214 {
3215 sound2_flags |= VEHICLE_SOUND2_FLAGS_LIFT_HILL;
3216 if (curRide->mode != RideMode::ReverseInclineLaunchedShuttle)
3217 {
3218 int32_t curSpeed = curRide->lift_hill_speed * 31079;
3219 if (velocity <= curSpeed)
3220 {
3221 acceleration = 15539;
3222 if (velocity != 0)
3223 {
3224 if (_vehicleBreakdown == BREAKDOWN_SAFETY_CUT_OUT)
3225 {
3226 SetUpdateFlag(VEHICLE_UPDATE_FLAG_ZERO_VELOCITY);
3227 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_COLLISION_DISABLED);
3228 }
3229 }
3230 else
3231 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_COLLISION_DISABLED);
3232 }
3233 }
3234 else
3235 {
3236 int32_t curSpeed = curRide->lift_hill_speed * -31079;
3237 if (velocity >= curSpeed)
3238 {
3239 acceleration = -15539;
3240 if (velocity != 0)
3241 {
3242 if (_vehicleBreakdown == BREAKDOWN_SAFETY_CUT_OUT)
3243 {
3244 SetUpdateFlag(VEHICLE_UPDATE_FLAG_ZERO_VELOCITY);
3245 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_COLLISION_DISABLED);
3246 }
3247 }
3248 else
3249 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_COLLISION_DISABLED);
3250 }
3251 }
3252 }
3253
3254 if (curRide->mode == RideMode::FreefallDrop)
3255 {
3256 animation_frame++;
3257 }
3258 else
3259 {
3260 bool shouldLaunch = true;
3261 if (curRide->mode == RideMode::DownwardLaunch)
3262 {
3263 if (var_CE < 1)
3264 shouldLaunch = false;
3265 }
3266
3267 if (shouldLaunch)
3268 {
3269 if (!(curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_3) || _vehicleStationIndex != current_station)
3270 {
3271 FinishDeparting();
3272 return;
3273 }
3274
3275 if (!(curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_5))
3276 return;
3277 if (curRide->mode == RideMode::BoatHire || curRide->mode == RideMode::RotatingLift
3278 || curRide->mode == RideMode::Shuttle)
3279 return;
3280
3281 UpdateCrashSetup();
3282 return;
3283 }
3284 }
3285
3286 if (!CurrentTowerElementIsTop())
3287 {
3288 if (curRide->mode == RideMode::FreefallDrop)
3289 Invalidate();
3290 return;
3291 }
3292
3293 FinishDeparting();
3294 }
3295
3296 /**
3297 * Part of UpdateDeparting
3298 * Called after finishing departing sequence to enter
3299 * traveling state.
3300 * Vertical rides class the lift to the top of the tower
3301 * as the departing sequence. After this point station
3302 * boosters do not affect the ride.
3303 * rct2: 0x006D8858
3304 */
FinishDeparting()3305 void Vehicle::FinishDeparting()
3306 {
3307 auto curRide = GetRide();
3308 if (curRide == nullptr)
3309 return;
3310
3311 if (curRide->mode == RideMode::DownwardLaunch)
3312 {
3313 if (var_CE >= 1 && (14 << 16) > velocity)
3314 return;
3315
3316 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::RideLaunch1, GetLocation());
3317 }
3318
3319 if (curRide->mode == RideMode::UpwardLaunch)
3320 {
3321 if ((curRide->launch_speed << 16) > velocity)
3322 return;
3323
3324 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::RideLaunch1, GetLocation());
3325 }
3326
3327 if (curRide->mode != RideMode::Race && !curRide->IsBlockSectioned())
3328 {
3329 curRide->stations[current_station].Depart &= STATION_DEPART_FLAG;
3330 uint8_t waitingTime = 3;
3331 if (curRide->depart_flags & RIDE_DEPART_WAIT_FOR_MINIMUM_LENGTH)
3332 {
3333 waitingTime = std::max(curRide->min_waiting_time, static_cast<uint8_t>(3));
3334 waitingTime = std::min(waitingTime, static_cast<uint8_t>(127));
3335 }
3336
3337 curRide->stations[current_station].Depart |= waitingTime;
3338 }
3339 lost_time_out = 0;
3340 SetState(Vehicle::Status::Travelling, 1);
3341 if (velocity < 0)
3342 sub_state = 0;
3343 }
3344
3345 /**
3346 *
3347 * rct2: 0x006DE5CB
3348 */
CheckIfMissing()3349 void Vehicle::CheckIfMissing()
3350 {
3351 auto curRide = GetRide();
3352 if (curRide == nullptr)
3353 return;
3354
3355 if (curRide->lifecycle_flags & (RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED))
3356 return;
3357
3358 if (curRide->IsBlockSectioned())
3359 return;
3360
3361 if (!curRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_CHECK_FOR_STALLING))
3362 return;
3363
3364 lost_time_out++;
3365 if (curRide->lifecycle_flags & RIDE_LIFECYCLE_HAS_STALLED_VEHICLE)
3366 return;
3367
3368 uint16_t limit = curRide->type == RIDE_TYPE_BOAT_HIRE ? 15360 : 9600;
3369
3370 if (lost_time_out <= limit)
3371 return;
3372
3373 curRide->lifecycle_flags |= RIDE_LIFECYCLE_HAS_STALLED_VEHICLE;
3374
3375 if (gConfigNotifications.ride_stalled_vehicles)
3376 {
3377 Formatter ft;
3378 ft.Add<rct_string_id>(GetRideComponentName(GetRideTypeDescriptor(curRide->type).NameConvention.vehicle).number);
3379
3380 uint8_t vehicleIndex = 0;
3381 for (; vehicleIndex < curRide->num_vehicles; ++vehicleIndex)
3382 if (curRide->vehicles[vehicleIndex] == sprite_index)
3383 break;
3384
3385 vehicleIndex++;
3386 ft.Add<uint16_t>(vehicleIndex);
3387 curRide->FormatNameTo(ft);
3388 ft.Add<rct_string_id>(GetRideComponentName(GetRideTypeDescriptor(curRide->type).NameConvention.station).singular);
3389
3390 News::AddItemToQueue(News::ItemType::Ride, STR_NEWS_VEHICLE_HAS_STALLED, EnumValue(ride), ft);
3391 }
3392 }
3393
SimulateCrash() const3394 void Vehicle::SimulateCrash() const
3395 {
3396 auto curRide = GetRide();
3397 if (curRide != nullptr)
3398 {
3399 curRide->lifecycle_flags |= RIDE_LIFECYCLE_CRASHED;
3400 }
3401 }
3402
3403 /**
3404 * Setup function for a vehicle colliding with
3405 * another vehicle.
3406 *
3407 * rct2: 0x006DA059
3408 */
UpdateCollisionSetup()3409 void Vehicle::UpdateCollisionSetup()
3410 {
3411 auto curRide = GetRide();
3412 if (curRide == nullptr)
3413 return;
3414
3415 if (curRide->status == RideStatus::Simulating)
3416 {
3417 SimulateCrash();
3418 return;
3419 }
3420
3421 SetState(Vehicle::Status::Crashed, sub_state);
3422
3423 if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_CRASHED))
3424 {
3425 auto frontVehicle = GetHead();
3426 auto trainIndex = ride_get_train_index_from_vehicle(curRide, frontVehicle->sprite_index);
3427 if (!trainIndex.has_value())
3428 {
3429 return;
3430 }
3431
3432 curRide->Crash(trainIndex.value());
3433
3434 if (curRide->status != RideStatus::Closed)
3435 {
3436 // We require this to execute right away during the simulation, always ignore network and queue.
3437 auto gameAction = RideSetStatusAction(curRide->id, RideStatus::Closed);
3438 GameActions::ExecuteNested(&gameAction);
3439 }
3440 }
3441
3442 curRide->lifecycle_flags |= RIDE_LIFECYCLE_CRASHED;
3443 curRide->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST;
3444 KillAllPassengersInTrain();
3445
3446 Vehicle* lastVehicle = this;
3447 for (Vehicle* train = GetEntity<Vehicle>(sprite_index); train != nullptr;
3448 train = GetEntity<Vehicle>(train->next_vehicle_on_train))
3449 {
3450 lastVehicle = train;
3451
3452 train->sub_state = 2;
3453
3454 #ifdef ENABLE_SCRIPTING
3455 InvokeVehicleCrashHook(train->sprite_index, "another_vehicle");
3456 #endif
3457 const auto trainLoc = train->GetLocation();
3458
3459 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::Crash, trainLoc);
3460
3461 ExplosionCloud::Create(trainLoc);
3462
3463 for (int32_t i = 0; i < 10; i++)
3464 {
3465 VehicleCrashParticle::Create(train->colours, trainLoc);
3466 }
3467
3468 train->IsCrashedVehicle = true;
3469 train->animationState = scenario_rand() & 0xFFFF;
3470
3471 train->animation_frame = scenario_rand() & 0x7;
3472 train->sprite_width = 13;
3473 train->sprite_height_negative = 45;
3474 train->sprite_height_positive = 5;
3475
3476 train->MoveTo(trainLoc);
3477
3478 train->SwingSpeed = 0;
3479 }
3480
3481 // Remove the current train from the ride linked list of trains
3482 auto prevTrain = GetEntity<Vehicle>(prev_vehicle_on_ride);
3483 auto nextTrain = GetEntity<Vehicle>(lastVehicle->next_vehicle_on_ride);
3484 if (prevTrain == nullptr || nextTrain == nullptr)
3485 {
3486 log_error("Corrupted vehicle list for ride!");
3487 }
3488 else
3489 {
3490 prevTrain->next_vehicle_on_ride = lastVehicle->next_vehicle_on_ride;
3491 nextTrain->prev_vehicle_on_ride = prev_vehicle_on_ride;
3492 }
3493
3494 velocity = 0;
3495 }
3496
3497 /** rct2: 0x009A3AC4, 0x009A3AC6 */
3498 static constexpr const CoordsXY stru_9A3AC4[] = {
3499 { -256, 0 }, { -236, 98 }, { -181, 181 }, { -98, 236 }, { 0, 256 }, { 98, 236 }, { 181, 181 }, { 236, 98 },
3500 { 256, 0 }, { 236, -98 }, { 181, -181 }, { 98, -236 }, { 0, -256 }, { -98, -236 }, { -181, -181 }, { -236, -98 },
3501 };
3502
3503 /**
3504 *
3505 * rct2: 0x006D9EFE
3506 */
UpdateCrashSetup()3507 void Vehicle::UpdateCrashSetup()
3508 {
3509 auto curRide = GetRide();
3510 if (curRide != nullptr && curRide->status == RideStatus::Simulating)
3511 {
3512 SimulateCrash();
3513 return;
3514 }
3515 SetState(Vehicle::Status::Crashing, sub_state);
3516
3517 if (NumPeepsUntilTrainTail() != 0)
3518 {
3519 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::HauntedHouseScream2, GetLocation());
3520 }
3521
3522 int32_t edx = velocity >> 10;
3523
3524 Vehicle* lastVehicle = this;
3525 uint16_t spriteId = sprite_index;
3526 for (Vehicle* trainVehicle; spriteId != SPRITE_INDEX_NULL; spriteId = trainVehicle->next_vehicle_on_train)
3527 {
3528 trainVehicle = GetEntity<Vehicle>(spriteId);
3529 if (trainVehicle == nullptr)
3530 {
3531 break;
3532 }
3533 lastVehicle = trainVehicle;
3534
3535 trainVehicle->sub_state = 0;
3536 int32_t trainX = stru_9A3AC4[trainVehicle->sprite_direction / 2].x;
3537 int32_t trainY = stru_9A3AC4[trainVehicle->sprite_direction / 2].y;
3538 auto trainZ = Unk9A38D4[trainVehicle->Pitch] >> 23;
3539
3540 int32_t ecx = Unk9A37E4[trainVehicle->Pitch] >> 15;
3541 trainX *= ecx;
3542 trainY *= ecx;
3543 trainX >>= 16;
3544 trainY >>= 16;
3545 trainX *= edx;
3546 trainY *= edx;
3547 trainZ *= edx;
3548 trainX >>= 8;
3549 trainY >>= 8;
3550 trainZ >>= 8;
3551
3552 trainVehicle->crash_x = trainX;
3553 trainVehicle->crash_y = trainY;
3554 trainVehicle->crash_z = trainZ;
3555 trainVehicle->crash_x += (scenario_rand() & 0xF) - 8;
3556 trainVehicle->crash_y += (scenario_rand() & 0xF) - 8;
3557 trainVehicle->crash_z += (scenario_rand() & 0xF) - 8;
3558
3559 trainVehicle->TrackLocation = { 0, 0, 0 };
3560 }
3561
3562 // Remove the current train from the ride linked list of trains
3563 auto prevTrain = GetEntity<Vehicle>(prev_vehicle_on_ride);
3564 auto nextTrain = GetEntity<Vehicle>(lastVehicle->next_vehicle_on_ride);
3565 if (prevTrain == nullptr || nextTrain == nullptr)
3566 {
3567 log_error("Corrupted vehicle list for ride!");
3568 }
3569 else
3570 {
3571 prevTrain->next_vehicle_on_ride = lastVehicle->next_vehicle_on_ride;
3572 nextTrain->prev_vehicle_on_ride = prev_vehicle_on_ride;
3573 }
3574 velocity = 0;
3575 }
3576
3577 /**
3578 *
3579 * rct2: 0x006D8937
3580 */
UpdateTravelling()3581 void Vehicle::UpdateTravelling()
3582 {
3583 CheckIfMissing();
3584
3585 auto curRide = GetRide();
3586 if (curRide == nullptr || (_vehicleBreakdown == 0 && curRide->mode == RideMode::RotatingLift))
3587 return;
3588
3589 if (sub_state == 2)
3590 {
3591 velocity = 0;
3592 acceleration = 0;
3593 var_C0--;
3594 if (var_C0 == 0)
3595 sub_state = 0;
3596 }
3597
3598 if (curRide->mode == RideMode::FreefallDrop && animation_frame != 0)
3599 {
3600 animation_frame++;
3601 velocity = 0;
3602 acceleration = 0;
3603 Invalidate();
3604 return;
3605 }
3606
3607 uint32_t curFlags = UpdateTrackMotion(nullptr);
3608
3609 bool skipCheck = false;
3610 if (curFlags & (VEHICLE_UPDATE_MOTION_TRACK_FLAG_8 | VEHICLE_UPDATE_MOTION_TRACK_FLAG_9)
3611 && curRide->mode == RideMode::ReverseInclineLaunchedShuttle && sub_state == 0)
3612 {
3613 sub_state = 1;
3614 velocity = 0;
3615 skipCheck = true;
3616 }
3617
3618 if (!skipCheck)
3619 {
3620 if (curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_DERAILED)
3621 {
3622 UpdateCrashSetup();
3623 return;
3624 }
3625
3626 if (curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_COLLISION)
3627 {
3628 UpdateCollisionSetup();
3629 return;
3630 }
3631
3632 if (curFlags & (VEHICLE_UPDATE_MOTION_TRACK_FLAG_5 | VEHICLE_UPDATE_MOTION_TRACK_FLAG_12))
3633 {
3634 if (curRide->mode == RideMode::RotatingLift)
3635 {
3636 if (sub_state <= 1)
3637 {
3638 SetState(Vehicle::Status::Arriving, 1);
3639 var_C0 = 0;
3640 return;
3641 }
3642 }
3643 else if (curRide->mode == RideMode::BoatHire)
3644 {
3645 UpdateTravellingBoatHireSetup();
3646 return;
3647 }
3648 if (curRide->mode == RideMode::Shuttle)
3649 {
3650 update_flags ^= VEHICLE_UPDATE_FLAG_REVERSING_SHUTTLE;
3651 velocity = 0;
3652 }
3653 else
3654 {
3655 if (sub_state != 0)
3656 {
3657 UpdateCrashSetup();
3658 return;
3659 }
3660 sub_state = 1;
3661 velocity = 0;
3662 }
3663 }
3664 }
3665
3666 if (curRide->mode == RideMode::RotatingLift && sub_state <= 1)
3667 {
3668 if (sub_state == 0)
3669 {
3670 if (velocity >= -131940)
3671 acceleration = -3298;
3672 velocity = std::max(velocity, -131940);
3673 }
3674 else
3675 {
3676 if (CurrentTowerElementIsTop())
3677 {
3678 velocity = 0;
3679 sub_state = 2;
3680 var_C0 = 150;
3681 }
3682 else
3683 {
3684 if (velocity <= 131940)
3685 acceleration = 3298;
3686 }
3687 }
3688 }
3689
3690 if (curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_ON_LIFT_HILL)
3691 {
3692 if (curRide->mode == RideMode::ReverseInclineLaunchedShuttle)
3693 {
3694 if (sub_state == 0)
3695 {
3696 if (velocity != 0)
3697 sound2_flags |= VEHICLE_SOUND2_FLAGS_LIFT_HILL;
3698
3699 if (!HasUpdateFlag(VEHICLE_UPDATE_FLAG_12))
3700 {
3701 if (velocity >= curRide->lift_hill_speed * -31079)
3702 {
3703 acceleration = -15539;
3704
3705 if (_vehicleBreakdown == 0)
3706 {
3707 sound2_flags &= ~VEHICLE_SOUND2_FLAGS_LIFT_HILL;
3708 SetUpdateFlag(VEHICLE_UPDATE_FLAG_ZERO_VELOCITY);
3709 }
3710 }
3711 }
3712 }
3713 }
3714 else
3715 {
3716 sound2_flags |= VEHICLE_SOUND2_FLAGS_LIFT_HILL;
3717 if (velocity <= curRide->lift_hill_speed * 31079)
3718 {
3719 acceleration = 15539;
3720 if (velocity != 0)
3721 {
3722 if (_vehicleBreakdown == 0)
3723 {
3724 SetUpdateFlag(VEHICLE_UPDATE_FLAG_ZERO_VELOCITY);
3725 sound2_flags &= ~VEHICLE_SOUND2_FLAGS_LIFT_HILL;
3726 }
3727 }
3728 else
3729 {
3730 sound2_flags &= ~VEHICLE_SOUND2_FLAGS_LIFT_HILL;
3731 }
3732 }
3733 }
3734 }
3735
3736 if (!(curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_3))
3737 return;
3738
3739 if (curRide->mode == RideMode::ReverseInclineLaunchedShuttle && velocity >= 0 && !HasUpdateFlag(VEHICLE_UPDATE_FLAG_12))
3740 {
3741 return;
3742 }
3743
3744 if (curRide->mode == RideMode::PoweredLaunchPasstrough && velocity < 0)
3745 return;
3746
3747 SetState(Vehicle::Status::Arriving);
3748 current_station = _vehicleStationIndex;
3749 var_C0 = 0;
3750 if (velocity < 0)
3751 sub_state = 1;
3752 }
3753
UpdateArrivingPassThroughStation(const Ride & curRide,const rct_ride_entry_vehicle & vehicleEntry,bool stationBrakesWork)3754 void Vehicle::UpdateArrivingPassThroughStation(
3755 const Ride& curRide, const rct_ride_entry_vehicle& vehicleEntry, bool stationBrakesWork)
3756 {
3757 if (sub_state == 0)
3758 {
3759 if (curRide.mode == RideMode::Race && curRide.lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING)
3760 {
3761 return;
3762 }
3763
3764 if (velocity <= 131940)
3765 {
3766 acceleration = 3298;
3767 return;
3768 }
3769
3770 int32_t velocity_diff = velocity;
3771 if (velocity_diff >= 1572864)
3772 velocity_diff /= 8;
3773 else
3774 velocity_diff /= 16;
3775
3776 if (!stationBrakesWork)
3777 {
3778 return;
3779 }
3780
3781 if (curRide.num_circuits != 1)
3782 {
3783 if (num_laps + 1 < curRide.num_circuits)
3784 {
3785 return;
3786 }
3787 }
3788 velocity -= velocity_diff;
3789 acceleration = 0;
3790 }
3791 else
3792 {
3793 if (!(vehicleEntry.flags & VEHICLE_ENTRY_FLAG_POWERED) && velocity >= -131940)
3794 {
3795 acceleration = -3298;
3796 }
3797
3798 if (velocity >= -131940)
3799 {
3800 return;
3801 }
3802
3803 int32_t velocity_diff = velocity;
3804 if (velocity_diff < -1572864)
3805 velocity_diff /= 8;
3806 else
3807 velocity_diff /= 16;
3808
3809 if (!stationBrakesWork)
3810 {
3811 return;
3812 }
3813
3814 if (num_laps + 1 < curRide.num_circuits)
3815 {
3816 return;
3817 }
3818
3819 if (num_laps + 1 != curRide.num_circuits)
3820 {
3821 velocity -= velocity_diff;
3822 acceleration = 0;
3823 return;
3824 }
3825
3826 if (GetRideTypeDescriptor(curRide.type).HasFlag(RIDE_TYPE_FLAG_ALLOW_MULTIPLE_CIRCUITS)
3827 && curRide.mode != RideMode::Shuttle && curRide.mode != RideMode::PoweredLaunch)
3828 {
3829 SetUpdateFlag(VEHICLE_UPDATE_FLAG_12);
3830 }
3831 else
3832 {
3833 velocity -= velocity_diff;
3834 acceleration = 0;
3835 }
3836 }
3837 }
3838
3839 /**
3840 *
3841 * rct2: 0x006D8C36
3842 */
UpdateArriving()3843 void Vehicle::UpdateArriving()
3844 {
3845 auto curRide = GetRide();
3846 if (curRide == nullptr)
3847 return;
3848
3849 bool stationBrakesWork = true;
3850 uint32_t curFlags = 0;
3851
3852 switch (curRide->mode)
3853 {
3854 case RideMode::Swing:
3855 case RideMode::Rotation:
3856 case RideMode::ForwardRotation:
3857 case RideMode::BackwardRotation:
3858 case RideMode::FilmAvengingAviators:
3859 case RideMode::FilmThrillRiders:
3860 case RideMode::Beginners:
3861 case RideMode::Intense:
3862 case RideMode::Berserk:
3863 case RideMode::MouseTails3DFilm:
3864 case RideMode::StormChasers3DFilm:
3865 case RideMode::SpaceRaiders3DFilm:
3866 case RideMode::Circus:
3867 case RideMode::SpaceRings:
3868 case RideMode::HauntedHouse:
3869 case RideMode::CrookedHouse:
3870 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_12);
3871 velocity = 0;
3872 acceleration = 0;
3873 SetState(Vehicle::Status::UnloadingPassengers);
3874 return;
3875 default:
3876 {
3877 // This is workaround for multiple compilation errors of type "enumeration value ‘RIDE_MODE_*' not handled
3878 // in switch [-Werror=switch]"
3879 }
3880 }
3881
3882 bool hasBrakesFailure = curRide->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN
3883 && curRide->breakdown_reason_pending == BREAKDOWN_BRAKES_FAILURE;
3884 if (hasBrakesFailure && curRide->inspection_station == current_station
3885 && curRide->mechanic_status != RIDE_MECHANIC_STATUS_HAS_FIXED_STATION_BRAKES)
3886 {
3887 stationBrakesWork = false;
3888 }
3889
3890 rct_ride_entry* rideEntry = GetRideEntry();
3891 rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle_type];
3892
3893 UpdateArrivingPassThroughStation(*curRide, *vehicleEntry, stationBrakesWork);
3894
3895 curFlags = UpdateTrackMotion(nullptr);
3896 if (curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_COLLISION && !stationBrakesWork)
3897 {
3898 UpdateCollisionSetup();
3899 return;
3900 }
3901
3902 if (curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_STATION && !stationBrakesWork)
3903 {
3904 SetState(Vehicle::Status::Departing, 1);
3905 return;
3906 }
3907
3908 if (!(curFlags
3909 & (VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_STATION | VEHICLE_UPDATE_MOTION_TRACK_FLAG_1
3910 | VEHICLE_UPDATE_MOTION_TRACK_FLAG_5)))
3911 {
3912 if (velocity > 98955)
3913 var_C0 = 0;
3914 return;
3915 }
3916
3917 var_C0++;
3918 if ((curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_1) && (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_GO_KART) && (var_C0 < 40))
3919 {
3920 return;
3921 }
3922
3923 auto trackElement = map_get_track_element_at(TrackLocation);
3924
3925 if (trackElement == nullptr)
3926 {
3927 return;
3928 }
3929
3930 current_station = trackElement->GetStationIndex();
3931 num_laps++;
3932
3933 if (sub_state != 0)
3934 {
3935 if (num_laps < curRide->num_circuits)
3936 {
3937 SetState(Vehicle::Status::Departing, 1);
3938 return;
3939 }
3940
3941 if (num_laps == curRide->num_circuits && HasUpdateFlag(VEHICLE_UPDATE_FLAG_12))
3942 {
3943 SetState(Vehicle::Status::Departing, 1);
3944 return;
3945 }
3946 }
3947
3948 if (curRide->num_circuits != 1 && num_laps < curRide->num_circuits)
3949 {
3950 SetState(Vehicle::Status::Departing, 1);
3951 return;
3952 }
3953
3954 if ((curRide->mode == RideMode::UpwardLaunch || curRide->mode == RideMode::DownwardLaunch) && var_CE < 2)
3955 {
3956 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::RideLaunch2, GetLocation());
3957 velocity = 0;
3958 acceleration = 0;
3959 SetState(Vehicle::Status::Departing, 1);
3960 return;
3961 }
3962
3963 if (curRide->mode == RideMode::Race && curRide->lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING)
3964 {
3965 SetState(Vehicle::Status::Departing, 1);
3966 return;
3967 }
3968
3969 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_12);
3970 velocity = 0;
3971 acceleration = 0;
3972 SetState(Vehicle::Status::UnloadingPassengers);
3973 }
3974
3975 /**
3976 *
3977 * rct2: 0x006D9002
3978 */
UpdateUnloadingPassengers()3979 void Vehicle::UpdateUnloadingPassengers()
3980 {
3981 if (sub_state == 0)
3982 {
3983 if (OpenRestraints())
3984 {
3985 sub_state = 1;
3986 }
3987 }
3988
3989 auto curRide = GetRide();
3990 if (curRide == nullptr)
3991 return;
3992
3993 if (curRide->mode == RideMode::ForwardRotation || curRide->mode == RideMode::BackwardRotation)
3994 {
3995 uint8_t seat = ((-Pitch) >> 3) & 0xF;
3996 if (restraints_position == 255 && (peep[seat * 2] != SPRITE_INDEX_NULL))
3997 {
3998 next_free_seat -= 2;
3999
4000 auto firstGuest = GetEntity<Guest>(peep[seat * 2]);
4001 peep[seat * 2] = SPRITE_INDEX_NULL;
4002
4003 if (firstGuest != nullptr)
4004 {
4005 firstGuest->SetState(PeepState::LeavingRide);
4006 firstGuest->RideSubState = PeepRideSubState::LeaveVehicle;
4007 }
4008
4009 auto secondGuest = GetEntity<Guest>(peep[seat * 2 + 1]);
4010 peep[seat * 2 + 1] = SPRITE_INDEX_NULL;
4011
4012 if (secondGuest != nullptr)
4013 {
4014 secondGuest->SetState(PeepState::LeavingRide);
4015 secondGuest->RideSubState = PeepRideSubState::LeaveVehicle;
4016 }
4017 }
4018 }
4019 else
4020 {
4021 if (ride_get_exit_location(curRide, current_station).IsNull())
4022 {
4023 if (sub_state != 1)
4024 return;
4025
4026 if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_TESTED) && HasUpdateFlag(VEHICLE_UPDATE_FLAG_TESTING)
4027 && curRide->current_test_segment + 1 >= curRide->num_stations)
4028 {
4029 UpdateTestFinish();
4030 }
4031 SetState(Vehicle::Status::MovingToEndOfStation);
4032 return;
4033 }
4034
4035 for (Vehicle* train = GetEntity<Vehicle>(sprite_index); train != nullptr;
4036 train = GetEntity<Vehicle>(train->next_vehicle_on_train))
4037 {
4038 if (train->restraints_position != 255)
4039 continue;
4040
4041 if (train->next_free_seat == 0)
4042 continue;
4043
4044 train->next_free_seat = 0;
4045 for (uint8_t peepIndex = 0; peepIndex < train->num_peeps; peepIndex++)
4046 {
4047 Peep* curPeep = GetEntity<Guest>(train->peep[peepIndex]);
4048 if (curPeep != nullptr)
4049 {
4050 curPeep->SetState(PeepState::LeavingRide);
4051 curPeep->RideSubState = PeepRideSubState::LeaveVehicle;
4052 }
4053 }
4054 }
4055 }
4056
4057 if (sub_state != 1)
4058 return;
4059
4060 for (Vehicle* train = GetEntity<Vehicle>(sprite_index); train != nullptr;
4061 train = GetEntity<Vehicle>(train->next_vehicle_on_train))
4062 {
4063 if (train->num_peeps != train->next_free_seat)
4064 return;
4065 }
4066
4067 if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_TESTED) && HasUpdateFlag(VEHICLE_UPDATE_FLAG_TESTING)
4068 && curRide->current_test_segment + 1 >= curRide->num_stations)
4069 {
4070 UpdateTestFinish();
4071 }
4072 SetState(Vehicle::Status::MovingToEndOfStation);
4073 }
4074
4075 /**
4076 *
4077 * rct2: 0x006D9CE9
4078 */
UpdateWaitingForCableLift()4079 void Vehicle::UpdateWaitingForCableLift()
4080 {
4081 auto curRide = GetRide();
4082 if (curRide == nullptr)
4083 return;
4084
4085 Vehicle* cableLift = GetEntity<Vehicle>(curRide->cable_lift);
4086 if (cableLift == nullptr)
4087 return;
4088
4089 if (cableLift->status != Vehicle::Status::WaitingForPassengers)
4090 return;
4091
4092 cableLift->SetState(Vehicle::Status::WaitingToDepart, sub_state);
4093 cableLift->cable_lift_target = sprite_index;
4094 }
4095
4096 /**
4097 *
4098 * rct2: 0x006D9D21
4099 */
UpdateTravellingCableLift()4100 void Vehicle::UpdateTravellingCableLift()
4101 {
4102 auto curRide = GetRide();
4103 if (curRide == nullptr)
4104 return;
4105
4106 if (sub_state == 0)
4107 {
4108 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_TRAIN))
4109 {
4110 if (curRide->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
4111 return;
4112
4113 curRide->lifecycle_flags |= RIDE_LIFECYCLE_BROKEN_DOWN;
4114 ride_breakdown_add_news_item(curRide);
4115 curRide->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST
4116 | RIDE_INVALIDATE_RIDE_MAINTENANCE;
4117
4118 curRide->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
4119 curRide->inspection_station = current_station;
4120 curRide->breakdown_reason = curRide->breakdown_reason_pending;
4121 velocity = 0;
4122 return;
4123 }
4124
4125 sub_state = 1;
4126 PeepEasterEggHereWeAre();
4127 if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_TESTED))
4128 {
4129 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_TESTING))
4130 {
4131 if (curRide->current_test_segment + 1 < curRide->num_stations)
4132 {
4133 curRide->current_test_segment++;
4134 curRide->current_test_station = current_station;
4135 }
4136 else
4137 {
4138 UpdateTestFinish();
4139 }
4140 }
4141 else if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_TEST_IN_PROGRESS) && !IsGhost())
4142 {
4143 TestReset();
4144 }
4145 }
4146 }
4147
4148 if (velocity <= 439800)
4149 {
4150 acceleration = 4398;
4151 }
4152 int32_t curFlags = UpdateTrackMotion(nullptr);
4153
4154 if (curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_11)
4155 {
4156 SetState(Vehicle::Status::Travelling, 1);
4157 lost_time_out = 0;
4158 return;
4159 }
4160
4161 if (sub_state == 2)
4162 return;
4163
4164 if (curFlags & VEHICLE_UPDATE_MOTION_TRACK_FLAG_3 && current_station == _vehicleStationIndex)
4165 return;
4166
4167 sub_state = 2;
4168
4169 if (curRide->IsBlockSectioned())
4170 return;
4171
4172 // This is slightly different to the vanilla function
4173 curRide->stations[current_station].Depart &= STATION_DEPART_FLAG;
4174 uint8_t waitingTime = 3;
4175 if (curRide->depart_flags & RIDE_DEPART_WAIT_FOR_MINIMUM_LENGTH)
4176 {
4177 waitingTime = std::max(curRide->min_waiting_time, static_cast<uint8_t>(3));
4178 waitingTime = std::min(waitingTime, static_cast<uint8_t>(127));
4179 }
4180
4181 curRide->stations[current_station].Depart |= waitingTime;
4182 }
4183
4184 /**
4185 *
4186 * rct2: 0x006D9820
4187 */
UpdateTravellingBoat()4188 void Vehicle::UpdateTravellingBoat()
4189 {
4190 CheckIfMissing();
4191 UpdateMotionBoatHire();
4192 }
4193
TryReconnectBoatToTrack(const CoordsXY & currentBoatLocation,const CoordsXY & trackCoords)4194 void Vehicle::TryReconnectBoatToTrack(const CoordsXY& currentBoatLocation, const CoordsXY& trackCoords)
4195 {
4196 remaining_distance = 0;
4197 if (!UpdateMotionCollisionDetection({ currentBoatLocation, z }, nullptr))
4198 {
4199 TrackLocation.x = trackCoords.x;
4200 TrackLocation.y = trackCoords.y;
4201
4202 auto trackElement = map_get_track_element_at(TrackLocation);
4203
4204 auto curRide = GetRide();
4205 if (curRide != nullptr)
4206 {
4207 SetTrackType(trackElement->GetTrackType());
4208 SetTrackDirection(curRide->boat_hire_return_direction);
4209 BoatLocation.SetNull();
4210 }
4211
4212 track_progress = 0;
4213 SetState(Vehicle::Status::Travelling, sub_state);
4214 unk_F64E20.x = currentBoatLocation.x;
4215 unk_F64E20.y = currentBoatLocation.y;
4216 }
4217 }
4218
4219 /**
4220 *
4221 * rct2: 0x006DA717
4222 */
UpdateMotionBoatHire()4223 void Vehicle::UpdateMotionBoatHire()
4224 {
4225 _vehicleMotionTrackFlags = 0;
4226 velocity += acceleration;
4227 _vehicleVelocityF64E08 = velocity;
4228 _vehicleVelocityF64E0C = (velocity >> 10) * 42;
4229
4230 auto vehicleEntry = Entry();
4231 if (vehicleEntry == nullptr)
4232 {
4233 return;
4234 }
4235 if (vehicleEntry->flags & (VEHICLE_ENTRY_FLAG_VEHICLE_ANIMATION | VEHICLE_ENTRY_FLAG_RIDER_ANIMATION))
4236 {
4237 UpdateAdditionalAnimation();
4238 }
4239
4240 _vehicleUnkF64E10 = 1;
4241 acceleration = 0;
4242 remaining_distance += _vehicleVelocityF64E0C;
4243 if (remaining_distance >= 0x368A)
4244 {
4245 sound2_flags &= ~VEHICLE_SOUND2_FLAGS_LIFT_HILL;
4246 unk_F64E20 = GetLocation();
4247 Invalidate();
4248
4249 for (;;)
4250 {
4251 // loc_6DA7A5
4252 var_35++;
4253 auto loc = BoatLocation.ToTileCentre();
4254 CoordsXY loc2 = loc;
4255 uint8_t bl;
4256
4257 loc2.x -= x;
4258 if (loc2.x >= 0)
4259 {
4260 loc2.y -= y;
4261 if (loc2.y < 0)
4262 {
4263 // loc_6DA81A:
4264 loc2.y = -loc2.y;
4265 bl = 24;
4266 if (loc2.y <= loc2.x * 4)
4267 {
4268 bl = 16;
4269 if (loc2.x <= loc2.y * 4)
4270 {
4271 bl = 20;
4272 }
4273 }
4274 }
4275 else
4276 {
4277 bl = 8;
4278 if (loc2.y <= loc2.x * 4)
4279 {
4280 bl = 16;
4281 if (loc2.x <= loc2.y * 4)
4282 {
4283 bl = 12;
4284 }
4285 }
4286 }
4287 }
4288 else
4289 {
4290 loc2.y -= y;
4291 if (loc2.y < 0)
4292 {
4293 // loc_6DA83D:
4294 loc2.x = -loc2.x;
4295 loc2.y = -loc2.y;
4296 bl = 24;
4297 if (loc2.y <= loc2.x * 4)
4298 {
4299 bl = 0;
4300 if (loc2.x <= loc2.y * 4)
4301 {
4302 bl = 28;
4303 }
4304 }
4305 }
4306 else
4307 {
4308 loc2.x = -loc2.x;
4309 bl = 8;
4310 if (loc2.y <= loc2.x * 4)
4311 {
4312 bl = 0;
4313 if (loc2.x <= loc2.y * 4)
4314 {
4315 bl = 4;
4316 }
4317 }
4318 }
4319 }
4320
4321 // loc_6DA861:
4322 var_34 = bl;
4323 loc2.x += loc2.y;
4324 if (loc2.x <= 12)
4325 {
4326 UpdateBoatLocation();
4327 }
4328
4329 if (!(var_35 & (1 << 0)))
4330 {
4331 uint8_t spriteDirection = sprite_direction;
4332 if (spriteDirection != var_34)
4333 {
4334 uint8_t dl = (var_34 + 16 - spriteDirection) & 0x1E;
4335 if (dl >= 16)
4336 {
4337 spriteDirection += 2;
4338 if (dl > 24)
4339 {
4340 var_35--;
4341 }
4342 }
4343 else
4344 {
4345 spriteDirection -= 2;
4346 if (dl < 8)
4347 {
4348 var_35--;
4349 }
4350 }
4351
4352 sprite_direction = spriteDirection & 0x1E;
4353 }
4354 }
4355
4356 int32_t edi = (sprite_direction | (var_35 & 1)) & 0x1F;
4357 loc2 = { x + Unk9A36C4[edi].x, y + Unk9A36C4[edi].y };
4358 if (UpdateMotionCollisionDetection({ loc2, z }, nullptr))
4359 {
4360 remaining_distance = 0;
4361 if (sprite_direction == var_34)
4362 {
4363 sprite_direction ^= (1 << 4);
4364 UpdateBoatLocation();
4365 sprite_direction ^= (1 << 4);
4366 }
4367 break;
4368 }
4369
4370 auto flooredLocation = loc2.ToTileStart();
4371 if (flooredLocation != TrackLocation)
4372 {
4373 if (!vehicle_boat_is_location_accessible({ loc2, TrackLocation.z }))
4374 {
4375 // loc_6DA939:
4376 auto curRide = GetRide();
4377 if (curRide == nullptr)
4378 return;
4379
4380 bool do_loc_6DAA97 = false;
4381 if (sub_state != 1)
4382 {
4383 do_loc_6DAA97 = true;
4384 }
4385 else
4386 {
4387 auto flooredTileLoc = TileCoordsXY(flooredLocation);
4388 if (curRide->boat_hire_return_position != flooredTileLoc)
4389 {
4390 do_loc_6DAA97 = true;
4391 }
4392 }
4393
4394 // loc_6DAA97:
4395 if (do_loc_6DAA97)
4396 {
4397 remaining_distance = 0;
4398 if (sprite_direction == var_34)
4399 {
4400 UpdateBoatLocation();
4401 }
4402 break;
4403 }
4404
4405 if (!(curRide->boat_hire_return_direction & 1))
4406 {
4407 uint16_t tilePart = loc2.y % COORDS_XY_STEP;
4408 if (tilePart == COORDS_XY_HALF_TILE)
4409 {
4410 TryReconnectBoatToTrack(loc2, flooredLocation);
4411 break;
4412 }
4413 loc2 = unk_F64E20;
4414 if (tilePart <= COORDS_XY_HALF_TILE)
4415 {
4416 loc2.y += 1;
4417 }
4418 else
4419 {
4420 loc2.y -= 1;
4421 }
4422 }
4423 else
4424 {
4425 // loc_6DA9A2:
4426 uint16_t tilePart = loc2.x % COORDS_XY_STEP;
4427 if (tilePart == COORDS_XY_HALF_TILE)
4428 {
4429 TryReconnectBoatToTrack(loc2, flooredLocation);
4430 break;
4431 }
4432 loc2 = unk_F64E20;
4433 if (tilePart <= COORDS_XY_HALF_TILE)
4434 {
4435 loc2.x += 1;
4436 }
4437 else
4438 {
4439 loc2.x -= 1;
4440 }
4441 }
4442
4443 // loc_6DA9D1:
4444 remaining_distance = 0;
4445 if (!UpdateMotionCollisionDetection({ loc2, z }, nullptr))
4446 {
4447 unk_F64E20.x = loc2.x;
4448 unk_F64E20.y = loc2.y;
4449 }
4450 break;
4451 }
4452 TrackLocation = { flooredLocation, TrackLocation.z };
4453 }
4454
4455 remaining_distance -= Unk9A36C4[edi].distance;
4456 unk_F64E20.x = loc2.x;
4457 unk_F64E20.y = loc2.y;
4458 if (remaining_distance < 0x368A)
4459 {
4460 break;
4461 }
4462 _vehicleUnkF64E10++;
4463 }
4464
4465 MoveTo(unk_F64E20);
4466 }
4467
4468 // loc_6DAAC9:
4469 {
4470 int32_t edx = velocity >> 8;
4471 edx = (edx * edx);
4472 if (velocity < 0)
4473 {
4474 edx = -edx;
4475 }
4476 edx >>= 5;
4477
4478 // Hack to fix people messing with boat hire
4479 int32_t curMass = mass == 0 ? 1 : mass;
4480
4481 int32_t eax = ((velocity >> 1) + edx) / curMass;
4482 int32_t ecx = -eax;
4483 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_POWERED)
4484 {
4485 eax = speed << 14;
4486 int32_t ebx = (speed * curMass) >> 2;
4487 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_REVERSING_SHUTTLE))
4488 {
4489 eax = -eax;
4490 }
4491 eax -= velocity;
4492 edx = powered_acceleration * 2;
4493 ecx += (eax * edx) / ebx;
4494 }
4495 acceleration = ecx;
4496 }
4497 // eax = _vehicleMotionTrackFlags;
4498 // ebx = _vehicleStationIndex;
4499 }
4500
4501 /**
4502 *
4503 * rct2: 0x006DA280
4504 */
UpdateBoatLocation()4505 void Vehicle::UpdateBoatLocation()
4506 {
4507 auto curRide = GetRide();
4508 if (curRide == nullptr)
4509 return;
4510
4511 TileCoordsXY returnPosition = curRide->boat_hire_return_position;
4512 uint8_t returnDirection = curRide->boat_hire_return_direction & 3;
4513
4514 CoordsXY location = CoordsXY{ x, y } + CoordsDirectionDelta[returnDirection];
4515
4516 if (location.ToTileStart() == returnPosition.ToCoordsXY())
4517 {
4518 sub_state = 1;
4519 BoatLocation = location.ToTileStart();
4520 return;
4521 }
4522
4523 sub_state = 0;
4524 uint8_t curDirection = ((sprite_direction + 19) >> 3) & 3;
4525 uint8_t randDirection = scenario_rand() & 3;
4526
4527 if (lost_time_out > 1920)
4528 {
4529 if (scenario_rand() & 1)
4530 {
4531 CoordsXY destLocation = (returnPosition.ToCoordsXY() - CoordsDirectionDelta[returnDirection]).ToTileCentre();
4532
4533 destLocation.x -= x;
4534 destLocation.y -= y;
4535
4536 if (abs(destLocation.x) <= abs(destLocation.y))
4537 {
4538 randDirection = destLocation.y < 0 ? 3 : 1;
4539 }
4540 else
4541 {
4542 randDirection = destLocation.x < 0 ? 0 : 2;
4543 }
4544 }
4545 }
4546
4547 static constexpr const int8_t rotations[] = { 0, 1, -1, 2 };
4548 for (auto rotation : rotations)
4549 {
4550 if (randDirection + rotation == curDirection)
4551 {
4552 continue;
4553 }
4554
4555 auto trackLocation = TrackLocation;
4556 trackLocation += CoordsDirectionDelta[(randDirection + rotation) & 3];
4557
4558 if (!vehicle_boat_is_location_accessible(trackLocation))
4559 {
4560 continue;
4561 }
4562
4563 BoatLocation = trackLocation.ToTileStart();
4564 return;
4565 }
4566
4567 CoordsXY trackLocation = TrackLocation;
4568 trackLocation += CoordsDirectionDelta[curDirection & 3];
4569 BoatLocation = trackLocation.ToTileStart();
4570 }
4571
4572 /**
4573 *
4574 * rct2: 0x006DA22A
4575 */
vehicle_boat_is_location_accessible(const CoordsXYZ & location)4576 static bool vehicle_boat_is_location_accessible(const CoordsXYZ& location)
4577 {
4578 TileElement* tileElement = map_get_first_element_at(location);
4579 if (tileElement == nullptr)
4580 return false;
4581 do
4582 {
4583 if (tileElement->IsGhost())
4584 continue;
4585
4586 if (tileElement->GetType() == TILE_ELEMENT_TYPE_SURFACE)
4587 {
4588 int32_t waterZ = tileElement->AsSurface()->GetWaterHeight();
4589 if (location.z != waterZ)
4590 {
4591 return false;
4592 }
4593 }
4594 else
4595 {
4596 if (location.z > (tileElement->GetBaseZ() - (2 * COORDS_Z_STEP))
4597 && location.z < tileElement->GetClearanceZ() + (2 * COORDS_Z_STEP))
4598 {
4599 return false;
4600 }
4601 }
4602 } while (!(tileElement++)->IsLastForTile());
4603 return true;
4604 }
4605
4606 /**
4607 *
4608 * rct2: 0x006D9249
4609 */
UpdateSwinging()4610 void Vehicle::UpdateSwinging()
4611 {
4612 auto curRide = GetRide();
4613 if (curRide == nullptr)
4614 return;
4615
4616 auto rideEntry = GetRideEntry();
4617 if (rideEntry == nullptr)
4618 return;
4619
4620 // SubState for this ride means swinging state
4621 // 0 == first swing
4622 // 3 == full swing
4623 uint8_t swingState = sub_state;
4624 if (rideEntry->flags & RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_1)
4625 {
4626 swingState += 4;
4627 if (rideEntry->flags & RIDE_ENTRY_FLAG_ALTERNATIVE_SWING_MODE_2)
4628 swingState += 4;
4629 }
4630
4631 const int8_t* spriteMap = SwingingTimeToSpriteMaps[swingState];
4632 int8_t spriteType = spriteMap[current_time + 1];
4633
4634 // 0x80 indicates that a complete swing has been
4635 // completed and the next swing can start
4636 if (spriteType != -128)
4637 {
4638 current_time++;
4639 if (static_cast<uint8_t>(spriteType) != Pitch)
4640 {
4641 // Used to know which sprite to draw
4642 Pitch = static_cast<uint8_t>(spriteType);
4643 Invalidate();
4644 }
4645 return;
4646 }
4647
4648 current_time = -1;
4649 var_CE++;
4650 if (curRide->status != RideStatus::Closed)
4651 {
4652 // It takes 3 swings to get into full swing
4653 // ride->rotations already takes this into account
4654 if (var_CE + 3 < curRide->rotations)
4655 {
4656 // Go to the next swing state until we
4657 // are at full swing.
4658 if (sub_state != 3)
4659 {
4660 sub_state++;
4661 }
4662 UpdateSwinging();
4663 return;
4664 }
4665 }
4666
4667 // To get to this part of the code the
4668 // swing has to be in slowing down phase
4669 if (sub_state == 0)
4670 {
4671 SetState(Vehicle::Status::Arriving);
4672 var_C0 = 0;
4673 return;
4674 }
4675 // Go towards first swing state
4676 sub_state--;
4677 UpdateSwinging();
4678 }
4679
4680 /**
4681 *
4682 * rct2: 0x006D9413
4683 */
UpdateFerrisWheelRotating()4684 void Vehicle::UpdateFerrisWheelRotating()
4685 {
4686 if (_vehicleBreakdown == 0)
4687 return;
4688
4689 auto curRide = GetRide();
4690 if (curRide == nullptr)
4691 return;
4692
4693 if ((ferris_wheel_var_1 -= 1) != 0)
4694 return;
4695
4696 int8_t curFerrisWheelVar0 = ferris_wheel_var_0;
4697
4698 if (curFerrisWheelVar0 == 3)
4699 {
4700 ferris_wheel_var_0 = curFerrisWheelVar0;
4701 ferris_wheel_var_1 = curFerrisWheelVar0;
4702 }
4703 else if (curFerrisWheelVar0 < 3)
4704 {
4705 if (curFerrisWheelVar0 != -8)
4706 curFerrisWheelVar0--;
4707 ferris_wheel_var_0 = curFerrisWheelVar0;
4708 ferris_wheel_var_1 = -curFerrisWheelVar0;
4709 }
4710 else
4711 {
4712 curFerrisWheelVar0--;
4713 ferris_wheel_var_0 = curFerrisWheelVar0;
4714 ferris_wheel_var_1 = curFerrisWheelVar0;
4715 }
4716
4717 uint8_t rotation = Pitch;
4718 if (curRide->mode == RideMode::ForwardRotation)
4719 rotation++;
4720 else
4721 rotation--;
4722
4723 rotation &= 0x7F;
4724 Pitch = rotation;
4725
4726 if (rotation == sub_state)
4727 var_CE++;
4728
4729 Invalidate();
4730
4731 uint8_t subState = sub_state;
4732 if (curRide->mode == RideMode::ForwardRotation)
4733 subState++;
4734 else
4735 subState--;
4736 subState &= 0x7F;
4737
4738 if (subState == Pitch)
4739 {
4740 bool shouldStop = true;
4741 if (curRide->status != RideStatus::Closed)
4742 {
4743 if (var_CE < curRide->rotations)
4744 shouldStop = false;
4745 }
4746
4747 if (shouldStop)
4748 {
4749 curFerrisWheelVar0 = ferris_wheel_var_0;
4750 ferris_wheel_var_0 = -abs(curFerrisWheelVar0);
4751 ferris_wheel_var_1 = abs(curFerrisWheelVar0);
4752 }
4753 }
4754
4755 if (ferris_wheel_var_0 != -8)
4756 return;
4757
4758 subState = sub_state;
4759 if (curRide->mode == RideMode::ForwardRotation)
4760 subState += 8;
4761 else
4762 subState -= 8;
4763 subState &= 0x7F;
4764
4765 if (subState != Pitch)
4766 return;
4767
4768 SetState(Vehicle::Status::Arriving);
4769 var_C0 = 0;
4770 }
4771
4772 /**
4773 *
4774 * rct2: 0x006D94F2
4775 */
UpdateSimulatorOperating()4776 void Vehicle::UpdateSimulatorOperating()
4777 {
4778 if (_vehicleBreakdown == 0)
4779 return;
4780
4781 assert(current_time >= -1);
4782 assert(current_time < MotionSimulatorTimeToSpriteMapCount);
4783 uint8_t al = MotionSimulatorTimeToSpriteMap[current_time + 1];
4784 if (al != 0xFF)
4785 {
4786 current_time++;
4787 if (al == Pitch)
4788 return;
4789 Pitch = al;
4790 Invalidate();
4791 return;
4792 }
4793
4794 SetState(Vehicle::Status::Arriving);
4795 var_C0 = 0;
4796 }
4797
4798 /**
4799 *
4800 * rct2: 0x006D92FF
4801 */
UpdateRotating()4802 void Vehicle::UpdateRotating()
4803 {
4804 if (_vehicleBreakdown == 0)
4805 return;
4806
4807 auto curRide = GetRide();
4808 if (curRide == nullptr)
4809 return;
4810
4811 auto rideEntry = GetRideEntry();
4812 if (rideEntry == nullptr)
4813 {
4814 return;
4815 }
4816
4817 const uint8_t* timeToSpriteMap;
4818 if (rideEntry->flags & RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_1)
4819 {
4820 timeToSpriteMap = Rotation1TimeToSpriteMaps[sub_state];
4821 }
4822 else if (rideEntry->flags & RIDE_ENTRY_FLAG_ALTERNATIVE_ROTATION_MODE_2)
4823 {
4824 timeToSpriteMap = Rotation2TimeToSpriteMaps[sub_state];
4825 }
4826 else
4827 {
4828 timeToSpriteMap = Rotation3TimeToSpriteMaps[sub_state];
4829 }
4830
4831 int32_t time = current_time;
4832 if (_vehicleBreakdown == BREAKDOWN_CONTROL_FAILURE)
4833 {
4834 time += (curRide->breakdown_sound_modifier >> 6) + 1;
4835 }
4836 time++;
4837
4838 uint8_t sprite = timeToSpriteMap[static_cast<uint32_t>(time)];
4839 if (sprite != 0xFF)
4840 {
4841 current_time = static_cast<uint16_t>(time);
4842 if (sprite == Pitch)
4843 return;
4844 Pitch = sprite;
4845 Invalidate();
4846 return;
4847 }
4848
4849 current_time = -1;
4850 var_CE++;
4851 if (_vehicleBreakdown != BREAKDOWN_CONTROL_FAILURE)
4852 {
4853 bool shouldStop = true;
4854 if (curRide->status != RideStatus::Closed)
4855 {
4856 sprite = var_CE + 1;
4857 if (curRide->type == RIDE_TYPE_ENTERPRISE)
4858 sprite += 9;
4859
4860 if (sprite < curRide->rotations)
4861 shouldStop = false;
4862 }
4863
4864 if (shouldStop)
4865 {
4866 if (sub_state == 2)
4867 {
4868 SetState(Vehicle::Status::Arriving);
4869 var_C0 = 0;
4870 return;
4871 }
4872 sub_state++;
4873 UpdateRotating();
4874 return;
4875 }
4876 }
4877
4878 if (curRide->type == RIDE_TYPE_ENTERPRISE && sub_state == 2)
4879 {
4880 SetState(Vehicle::Status::Arriving);
4881 var_C0 = 0;
4882 return;
4883 }
4884
4885 sub_state = 1;
4886 UpdateRotating();
4887 }
4888
4889 /**
4890 *
4891 * rct2: 0x006D97CB
4892 */
UpdateSpaceRingsOperating()4893 void Vehicle::UpdateSpaceRingsOperating()
4894 {
4895 if (_vehicleBreakdown == 0)
4896 return;
4897
4898 uint8_t spriteType = SpaceRingsTimeToSpriteMap[current_time + 1];
4899 if (spriteType != 255)
4900 {
4901 current_time++;
4902 if (spriteType != Pitch)
4903 {
4904 Pitch = spriteType;
4905 Invalidate();
4906 }
4907 }
4908 else
4909 {
4910 SetState(Vehicle::Status::Arriving);
4911 var_C0 = 0;
4912 }
4913 }
4914
4915 /**
4916 *
4917 * rct2: 0x006D9641
4918 */
UpdateHauntedHouseOperating()4919 void Vehicle::UpdateHauntedHouseOperating()
4920 {
4921 if (_vehicleBreakdown == 0)
4922 return;
4923
4924 if (Pitch != 0)
4925 {
4926 if (gCurrentTicks & 1)
4927 {
4928 Pitch++;
4929 Invalidate();
4930
4931 if (Pitch == 19)
4932 Pitch = 0;
4933 }
4934 }
4935
4936 if (current_time + 1 > 1500)
4937 {
4938 SetState(Vehicle::Status::Arriving);
4939 var_C0 = 0;
4940 return;
4941 }
4942
4943 current_time++;
4944 switch (current_time)
4945 {
4946 case 45:
4947 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::HauntedHouseScare, GetLocation());
4948 break;
4949 case 75:
4950 Pitch = 1;
4951 Invalidate();
4952 break;
4953 case 400:
4954 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::HauntedHouseScream1, GetLocation());
4955 break;
4956 case 745:
4957 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::HauntedHouseScare, GetLocation());
4958 break;
4959 case 775:
4960 Pitch = 1;
4961 Invalidate();
4962 break;
4963 case 1100:
4964 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::HauntedHouseScream2, GetLocation());
4965 break;
4966 }
4967 }
4968
4969 /**
4970 *
4971 * rct2: 0x006d9781
4972 */
UpdateCrookedHouseOperating()4973 void Vehicle::UpdateCrookedHouseOperating()
4974 {
4975 if (_vehicleBreakdown == 0)
4976 return;
4977
4978 // Originally used an array of size 1 at 0x009A0AC4 and passed the sub state into it.
4979 if (static_cast<uint16_t>(current_time + 1) > 600)
4980 {
4981 SetState(Vehicle::Status::Arriving);
4982 var_C0 = 0;
4983 return;
4984 }
4985
4986 current_time++;
4987 }
4988
4989 /**
4990 *
4991 * rct2: 0x006D9547
4992 */
UpdateTopSpinOperating()4993 void Vehicle::UpdateTopSpinOperating()
4994 {
4995 if (_vehicleBreakdown == 0)
4996 return;
4997
4998 const top_spin_time_to_sprite_map* sprite_map = TopSpinTimeToSpriteMaps[sub_state];
4999 uint8_t rotation = sprite_map[current_time + 1].arm_rotation;
5000 if (rotation != 0xFF)
5001 {
5002 current_time = current_time + 1;
5003 if (rotation != Pitch)
5004 {
5005 Pitch = rotation;
5006 Invalidate();
5007 }
5008 rotation = sprite_map[current_time].bank_rotation;
5009 if (rotation != bank_rotation)
5010 {
5011 bank_rotation = rotation;
5012 Invalidate();
5013 }
5014 return;
5015 }
5016
5017 SetState(Vehicle::Status::Arriving);
5018 var_C0 = 0;
5019 }
5020
5021 /**
5022 *
5023 * rct2: 0x006D95AD
5024 */
UpdateShowingFilm()5025 void Vehicle::UpdateShowingFilm()
5026 {
5027 int32_t currentTime, totalTime;
5028
5029 if (_vehicleBreakdown == 0)
5030 return;
5031
5032 totalTime = RideFilmLength[sub_state];
5033 currentTime = current_time + 1;
5034 if (currentTime <= totalTime)
5035 {
5036 current_time = currentTime;
5037 }
5038 else
5039 {
5040 SetState(Vehicle::Status::Arriving);
5041 var_C0 = 0;
5042 }
5043 }
5044
5045 /**
5046 *
5047 * rct2: 0x006D95F7
5048 */
UpdateDoingCircusShow()5049 void Vehicle::UpdateDoingCircusShow()
5050 {
5051 if (_vehicleBreakdown == 0)
5052 return;
5053
5054 int32_t currentTime = current_time + 1;
5055 if (currentTime <= 5000)
5056 {
5057 current_time = currentTime;
5058 }
5059 else
5060 {
5061 SetState(Vehicle::Status::Arriving);
5062 var_C0 = 0;
5063 }
5064 }
5065
5066 /**
5067 *
5068 * rct2: 0x0068B8BD
5069 * @returns the map element that the vehicle will collide with or NULL if no collisions.
5070 */
vehicle_check_collision(const CoordsXYZ & vehiclePosition)5071 static TileElement* vehicle_check_collision(const CoordsXYZ& vehiclePosition)
5072 {
5073 TileElement* tileElement = map_get_first_element_at(vehiclePosition);
5074 if (tileElement == nullptr)
5075 {
5076 return nullptr;
5077 }
5078
5079 uint8_t quadrant;
5080 if ((vehiclePosition.x & 0x1F) >= 16)
5081 {
5082 quadrant = 1;
5083 if ((vehiclePosition.y & 0x1F) < 16)
5084 quadrant = 2;
5085 }
5086 else
5087 {
5088 quadrant = 4;
5089 if ((vehiclePosition.y & 0x1F) >= 16)
5090 quadrant = 8;
5091 }
5092
5093 do
5094 {
5095 if (vehiclePosition.z < tileElement->GetBaseZ())
5096 continue;
5097
5098 if (vehiclePosition.z >= tileElement->GetClearanceZ())
5099 continue;
5100
5101 if (tileElement->GetOccupiedQuadrants() & quadrant)
5102 return tileElement;
5103 } while (!(tileElement++)->IsLastForTile());
5104
5105 return nullptr;
5106 }
5107
ride_train_crash(Ride * ride,uint16_t numFatalities)5108 static void ride_train_crash(Ride* ride, uint16_t numFatalities)
5109 {
5110 Formatter ft;
5111 ft.Add<uint16_t>(numFatalities);
5112
5113 uint8_t crashType = numFatalities == 0 ? RIDE_CRASH_TYPE_NO_FATALITIES : RIDE_CRASH_TYPE_FATALITIES;
5114
5115 if (crashType >= ride->last_crash_type)
5116 ride->last_crash_type = crashType;
5117
5118 if (numFatalities != 0)
5119 {
5120 if (gConfigNotifications.ride_casualties)
5121 {
5122 ride->FormatNameTo(ft);
5123 News::AddItemToQueue(
5124 News::ItemType::Ride, numFatalities == 1 ? STR_X_PERSON_DIED_ON_X : STR_X_PEOPLE_DIED_ON_X, EnumValue(ride->id),
5125 ft);
5126 }
5127
5128 if (gParkRatingCasualtyPenalty < 500)
5129 {
5130 gParkRatingCasualtyPenalty += 200;
5131 }
5132 }
5133 }
5134 /**
5135 *
5136 * rct2: 0x006DE6C6
5137 */
KillAllPassengersInTrain()5138 void Vehicle::KillAllPassengersInTrain()
5139 {
5140 auto curRide = GetRide();
5141 if (curRide == nullptr)
5142 return;
5143
5144 ride_train_crash(curRide, NumPeepsUntilTrainTail());
5145
5146 for (Vehicle* trainCar = GetEntity<Vehicle>(sprite_index); trainCar != nullptr;
5147 trainCar = GetEntity<Vehicle>(trainCar->next_vehicle_on_train))
5148 {
5149 trainCar->KillPassengers(curRide);
5150 }
5151 }
5152
KillPassengers(Ride * curRide)5153 void Vehicle::KillPassengers(Ride* curRide)
5154 {
5155 if (num_peeps != next_free_seat)
5156 return;
5157
5158 if (num_peeps == 0)
5159 return;
5160
5161 for (auto i = 0; i < num_peeps; i++)
5162 {
5163 auto* curPeep = GetEntity<Guest>(peep[i]);
5164 if (curPeep == nullptr)
5165 continue;
5166
5167 if (!curPeep->OutsideOfPark)
5168 {
5169 decrement_guests_in_park();
5170 auto intent = Intent(INTENT_ACTION_UPDATE_GUEST_COUNT);
5171 context_broadcast_intent(&intent);
5172 }
5173 peep_sprite_remove(curPeep);
5174 }
5175
5176 num_peeps = 0;
5177 next_free_seat = 0;
5178 }
5179
CrashOnLand()5180 void Vehicle::CrashOnLand()
5181 {
5182 auto curRide = GetRide();
5183 if (curRide == nullptr)
5184 return;
5185
5186 if (curRide->status == RideStatus::Simulating)
5187 {
5188 SimulateCrash();
5189 return;
5190 }
5191 SetState(Vehicle::Status::Crashed, sub_state);
5192
5193 #ifdef ENABLE_SCRIPTING
5194 InvokeVehicleCrashHook(sprite_index, "land");
5195 #endif
5196
5197 if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_CRASHED))
5198 {
5199 auto frontVehicle = GetHead();
5200 auto trainIndex = ride_get_train_index_from_vehicle(curRide, frontVehicle->sprite_index);
5201 if (!trainIndex.has_value())
5202 {
5203 return;
5204 }
5205
5206 curRide->Crash(trainIndex.value());
5207
5208 if (curRide->status != RideStatus::Closed)
5209 {
5210 // We require this to execute right away during the simulation, always ignore network and queue.
5211 auto gameAction = RideSetStatusAction(curRide->id, RideStatus::Closed);
5212 GameActions::ExecuteNested(&gameAction);
5213 }
5214 }
5215 curRide->lifecycle_flags |= RIDE_LIFECYCLE_CRASHED;
5216 curRide->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST;
5217
5218 if (IsHead())
5219 {
5220 KillAllPassengersInTrain();
5221 }
5222
5223 sub_state = 2;
5224
5225 const auto curLoc = GetLocation();
5226 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::Crash, curLoc);
5227
5228 ExplosionCloud::Create(curLoc);
5229 ExplosionFlare::Create(curLoc);
5230
5231 uint8_t numParticles = std::min(sprite_width, static_cast<uint8_t>(7));
5232
5233 while (numParticles-- != 0)
5234 VehicleCrashParticle::Create(colours, curLoc);
5235
5236 IsCrashedVehicle = true;
5237 animation_frame = 0;
5238 animationState = 0;
5239 sprite_width = 13;
5240 sprite_height_negative = 45;
5241 sprite_height_positive = 5;
5242
5243 MoveTo(curLoc);
5244
5245 crash_z = 0;
5246 }
5247
CrashOnWater()5248 void Vehicle::CrashOnWater()
5249 {
5250 auto curRide = GetRide();
5251 if (curRide == nullptr)
5252 return;
5253
5254 if (curRide->status == RideStatus::Simulating)
5255 {
5256 SimulateCrash();
5257 return;
5258 }
5259 SetState(Vehicle::Status::Crashed, sub_state);
5260
5261 #ifdef ENABLE_SCRIPTING
5262 InvokeVehicleCrashHook(sprite_index, "water");
5263 #endif
5264
5265 if (!(curRide->lifecycle_flags & RIDE_LIFECYCLE_CRASHED))
5266 {
5267 auto frontVehicle = GetHead();
5268 auto trainIndex = ride_get_train_index_from_vehicle(curRide, frontVehicle->sprite_index);
5269 if (!trainIndex.has_value())
5270 {
5271 return;
5272 }
5273
5274 curRide->Crash(trainIndex.value());
5275
5276 if (curRide->status != RideStatus::Closed)
5277 {
5278 // We require this to execute right away during the simulation, always ignore network and queue.
5279 auto gameAction = RideSetStatusAction(curRide->id, RideStatus::Closed);
5280 GameActions::ExecuteNested(&gameAction);
5281 }
5282 }
5283 curRide->lifecycle_flags |= RIDE_LIFECYCLE_CRASHED;
5284 curRide->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST;
5285
5286 if (IsHead())
5287 {
5288 KillAllPassengersInTrain();
5289 }
5290
5291 sub_state = 2;
5292
5293 const auto curLoc = GetLocation();
5294 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::Water1, curLoc);
5295
5296 CrashSplashParticle::Create(curLoc);
5297 CrashSplashParticle::Create(curLoc + CoordsXYZ{ -8, -9, 0 });
5298 CrashSplashParticle::Create(curLoc + CoordsXYZ{ 11, -9, 0 });
5299 CrashSplashParticle::Create(curLoc + CoordsXYZ{ 11, 8, 0 });
5300 CrashSplashParticle::Create(curLoc + CoordsXYZ{ -4, 8, 0 });
5301
5302 for (int32_t i = 0; i < 10; ++i)
5303 VehicleCrashParticle::Create(colours, curLoc + CoordsXYZ{ -4, 8, 0 });
5304
5305 IsCrashedVehicle = true;
5306 animation_frame = 0;
5307 animationState = 0;
5308 sprite_width = 13;
5309 sprite_height_negative = 45;
5310 sprite_height_positive = 5;
5311
5312 MoveTo(curLoc);
5313
5314 crash_z = -1;
5315 }
5316
5317 /**
5318 *
5319 * rct2: 0x006D98CA
5320 */
UpdateCrash()5321 void Vehicle::UpdateCrash()
5322 {
5323 for (Vehicle* curVehicle = GetEntity<Vehicle>(sprite_index); curVehicle != nullptr;
5324 curVehicle = GetEntity<Vehicle>(curVehicle->next_vehicle_on_train))
5325 {
5326 CoordsXYZ curPos = curVehicle->GetLocation();
5327
5328 if (curVehicle->sub_state > 1)
5329 {
5330 if (curVehicle->crash_z <= 96)
5331 {
5332 curVehicle->crash_z++;
5333 if ((scenario_rand() & 0xFFFF) <= 0x1555)
5334 {
5335 int32_t xOffset = (scenario_rand() & 2) - 1;
5336 int32_t yOffset = (scenario_rand() & 2) - 1;
5337
5338 ExplosionCloud::Create(curPos + CoordsXYZ{ xOffset, yOffset, 0 });
5339 }
5340 }
5341 if (curVehicle->animationState <= 0xe388)
5342 {
5343 curVehicle->animationState += 0x1c71;
5344 }
5345 else
5346 {
5347 curVehicle->animationState = 0;
5348 curVehicle->animation_frame++;
5349 if (curVehicle->animation_frame >= 8)
5350 curVehicle->animation_frame = 0;
5351 curVehicle->Invalidate();
5352 }
5353 continue;
5354 }
5355
5356 TileElement* collideElement = vehicle_check_collision(curPos);
5357 if (collideElement == nullptr)
5358 {
5359 curVehicle->sub_state = 1;
5360 }
5361 else if (curVehicle->sub_state == 1)
5362 {
5363 curVehicle->CrashOnLand();
5364 continue;
5365 }
5366
5367 int16_t height = tile_element_height(curPos);
5368 int16_t waterHeight = tile_element_water_height(curPos);
5369 int16_t zDiff;
5370 if (waterHeight != 0)
5371 {
5372 zDiff = curPos.z - waterHeight;
5373 if (zDiff <= 0 && zDiff >= -20)
5374 {
5375 curVehicle->CrashOnWater();
5376 continue;
5377 }
5378 }
5379
5380 zDiff = curPos.z - height;
5381 if ((zDiff <= 0 && zDiff >= -20) || curPos.z < 16)
5382 {
5383 curVehicle->CrashOnLand();
5384 continue;
5385 }
5386
5387 curVehicle->Invalidate();
5388
5389 curPos.x += static_cast<int8_t>(curVehicle->crash_x >> 8);
5390 curPos.y += static_cast<int8_t>(curVehicle->crash_y >> 8);
5391 curPos.z += static_cast<int8_t>(curVehicle->crash_z >> 8);
5392 curVehicle->TrackLocation = { (curVehicle->crash_x << 8), (curVehicle->crash_y << 8), (curVehicle->crash_z << 8) };
5393
5394 if (!map_is_location_valid(curPos))
5395 {
5396 curVehicle->CrashOnLand();
5397 continue;
5398 }
5399
5400 curVehicle->MoveTo(curPos);
5401
5402 if (curVehicle->sub_state == 1)
5403 {
5404 curVehicle->crash_z -= 20;
5405 }
5406 }
5407 }
5408 /**
5409 *
5410 * rct2: 0x006D7888
5411 */
UpdateSound()5412 void Vehicle::UpdateSound()
5413 {
5414 // frictionVolume (bl) should be set before hand
5415 SoundIdVolume frictionSound = { OpenRCT2::Audio::SoundId::Null, 255 };
5416 // bh screamVolume should be set before hand
5417 SoundIdVolume screamSound = { OpenRCT2::Audio::SoundId::Null, 255 };
5418
5419 auto curRide = GetRide();
5420 if (curRide == nullptr)
5421 return;
5422
5423 auto rideEntry = GetRideEntry();
5424 if (rideEntry == nullptr)
5425 return;
5426
5427 rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle_type];
5428
5429 int32_t ecx = abs(velocity) - 0x10000;
5430 if (ecx >= 0)
5431 {
5432 frictionSound.id = vehicleEntry->friction_sound_id;
5433 ecx >>= 15;
5434 frictionSound.volume = std::min(208 + (ecx & 0xFF), 255);
5435 }
5436
5437 switch (vehicleEntry->sound_range)
5438 {
5439 case SOUND_RANGE_WHISTLE:
5440 screamSound.id = scream_sound_id;
5441 if (!(gCurrentTicks & 0x7F))
5442 {
5443 if (velocity < 0x40000 || scream_sound_id != OpenRCT2::Audio::SoundId::Null)
5444 {
5445 GetLiftHillSound(curRide, screamSound);
5446 break;
5447 }
5448
5449 if ((scenario_rand() & 0xFFFF) <= 0x5555)
5450 {
5451 scream_sound_id = OpenRCT2::Audio::SoundId::TrainWhistle;
5452 screamSound.volume = 255;
5453 break;
5454 }
5455 }
5456 if (screamSound.id == OpenRCT2::Audio::SoundId::NoScream)
5457 screamSound.id = OpenRCT2::Audio::SoundId::Null;
5458 screamSound.volume = 255;
5459 break;
5460
5461 case SOUND_RANGE_BELL:
5462 screamSound.id = scream_sound_id;
5463 if (!(gCurrentTicks & 0x7F))
5464 {
5465 if (velocity < 0x40000 || scream_sound_id != OpenRCT2::Audio::SoundId::Null)
5466 {
5467 GetLiftHillSound(curRide, screamSound);
5468 break;
5469 }
5470
5471 if ((scenario_rand() & 0xFFFF) <= 0x5555)
5472 {
5473 scream_sound_id = OpenRCT2::Audio::SoundId::Tram;
5474 screamSound.volume = 255;
5475 break;
5476 }
5477 }
5478 if (screamSound.id == OpenRCT2::Audio::SoundId::NoScream)
5479 screamSound.id = OpenRCT2::Audio::SoundId::Null;
5480 screamSound.volume = 255;
5481 break;
5482
5483 default:
5484 if ((vehicleEntry->flags & VEHICLE_ENTRY_FLAG_RIDERS_SCREAM))
5485 {
5486 screamSound.id = UpdateScreamSound();
5487 if (screamSound.id == OpenRCT2::Audio::SoundId::NoScream)
5488 {
5489 screamSound.id = OpenRCT2::Audio::SoundId::Null;
5490 break;
5491 }
5492 if (screamSound.id != OpenRCT2::Audio::SoundId::Null)
5493 {
5494 break;
5495 }
5496 }
5497 GetLiftHillSound(curRide, screamSound);
5498 }
5499
5500 // Friction sound
5501 auto soundIdVolume = sub_6D7AC0(sound1_id, sound1_volume, frictionSound.id, frictionSound.volume);
5502 sound1_id = soundIdVolume.id;
5503 sound1_volume = soundIdVolume.volume;
5504
5505 // Scream sound
5506 soundIdVolume = sub_6D7AC0(sound2_id, sound2_volume, screamSound.id, screamSound.volume);
5507 sound2_id = soundIdVolume.id;
5508 sound2_volume = soundIdVolume.volume;
5509
5510 // Calculate Sound Vector (used for sound frequency calcs)
5511 int32_t soundDirection = SpriteDirectionToSoundDirection[sprite_direction];
5512 int32_t soundVector = ((velocity >> 14) * soundDirection) >> 14;
5513 soundVector = std::clamp(soundVector, -127, 127);
5514
5515 sound_vector_factor = soundVector & 0xFF;
5516 }
5517
5518 /**
5519 *
5520 * rct2: 0x006D796B
5521 */
UpdateScreamSound()5522 OpenRCT2::Audio::SoundId Vehicle::UpdateScreamSound()
5523 {
5524 int32_t totalNumPeeps = NumPeepsUntilTrainTail();
5525 if (totalNumPeeps == 0)
5526 return OpenRCT2::Audio::SoundId::Null;
5527
5528 if (velocity < 0)
5529 {
5530 if (velocity > -0x2C000)
5531 return OpenRCT2::Audio::SoundId::Null;
5532
5533 for (Vehicle* vehicle2 = GetEntity<Vehicle>(sprite_index); vehicle2 != nullptr;
5534 vehicle2 = GetEntity<Vehicle>(vehicle2->next_vehicle_on_train))
5535 {
5536 if (vehicle2->Pitch < 1)
5537 continue;
5538 if (vehicle2->Pitch <= 4)
5539 return ProduceScreamSound(totalNumPeeps);
5540 if (vehicle2->Pitch < 9)
5541 continue;
5542 if (vehicle2->Pitch <= 15)
5543 return ProduceScreamSound(totalNumPeeps);
5544 }
5545 return OpenRCT2::Audio::SoundId::Null;
5546 }
5547
5548 if (velocity < 0x2C000)
5549 return OpenRCT2::Audio::SoundId::Null;
5550
5551 for (Vehicle* vehicle2 = GetEntity<Vehicle>(sprite_index); vehicle2 != nullptr;
5552 vehicle2 = GetEntity<Vehicle>(vehicle2->next_vehicle_on_train))
5553 {
5554 if (vehicle2->Pitch < 5)
5555 continue;
5556 if (vehicle2->Pitch <= 8)
5557 return ProduceScreamSound(totalNumPeeps);
5558 if (vehicle2->Pitch < 17)
5559 continue;
5560 if (vehicle2->Pitch <= 23)
5561 return ProduceScreamSound(totalNumPeeps);
5562 }
5563 return OpenRCT2::Audio::SoundId::Null;
5564 }
5565
ProduceScreamSound(const int32_t totalNumPeeps)5566 OpenRCT2::Audio::SoundId Vehicle::ProduceScreamSound(const int32_t totalNumPeeps)
5567 {
5568 rct_ride_entry* rideEntry = GetRideEntry();
5569
5570 rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle_type];
5571
5572 if (scream_sound_id == OpenRCT2::Audio::SoundId::Null)
5573 {
5574 auto r = scenario_rand();
5575 if (totalNumPeeps >= static_cast<int32_t>(r % 16))
5576 {
5577 switch (vehicleEntry->sound_range)
5578 {
5579 case SOUND_RANGE_SCREAMS_0:
5580 scream_sound_id = byte_9A3A14[r % 2];
5581 break;
5582 case SOUND_RANGE_SCREAMS_1:
5583 scream_sound_id = byte_9A3A18[r % 7];
5584 break;
5585 case SOUND_RANGE_SCREAMS_2:
5586 scream_sound_id = byte_9A3A16[r % 2];
5587 break;
5588 default:
5589 scream_sound_id = OpenRCT2::Audio::SoundId::NoScream;
5590 break;
5591 }
5592 }
5593 else
5594 {
5595 scream_sound_id = OpenRCT2::Audio::SoundId::NoScream;
5596 }
5597 }
5598 return scream_sound_id;
5599 }
5600
5601 /**
5602 *
5603 * rct2: 0x006D73D0
5604 * ax: verticalG
5605 * dx: lateralG
5606 * esi: vehicle
5607 */
GetGForces() const5608 GForces Vehicle::GetGForces() const
5609 {
5610 int32_t gForceVert = ((static_cast<int64_t>(0x280000)) * Unk9A37E4[Pitch]) >> 32;
5611 gForceVert = ((static_cast<int64_t>(gForceVert)) * Unk9A39C4[bank_rotation]) >> 32;
5612 int32_t lateralFactor = 0, vertFactor = 0;
5613
5614 // Note shr has meant some of the below functions cast a known negative number to
5615 // unsigned. Possibly an original bug but will be left implemented.
5616 switch (GetTrackType())
5617 {
5618 case TrackElemType::Flat:
5619 case TrackElemType::EndStation:
5620 case TrackElemType::BeginStation:
5621 case TrackElemType::MiddleStation:
5622 case TrackElemType::Up25:
5623 case TrackElemType::Up60: //
5624 case TrackElemType::Down25:
5625 case TrackElemType::Down60: //
5626 case TrackElemType::FlatToLeftBank:
5627 case TrackElemType::FlatToRightBank:
5628 case TrackElemType::LeftBankToFlat:
5629 case TrackElemType::RightBankToFlat: //
5630 case TrackElemType::LeftBank:
5631 case TrackElemType::RightBank:
5632 case TrackElemType::TowerBase:
5633 case TrackElemType::TowerSection:
5634 case TrackElemType::FlatCovered:
5635 case TrackElemType::Up25Covered:
5636 case TrackElemType::Up60Covered:
5637 case TrackElemType::Down25Covered:
5638 case TrackElemType::Down60Covered:
5639 case TrackElemType::Brakes:
5640 case TrackElemType::RotationControlToggle:
5641 case TrackElemType::Maze:
5642 case TrackElemType::Up25LeftBanked:
5643 case TrackElemType::Up25RightBanked:
5644 case TrackElemType::Waterfall:
5645 case TrackElemType::Rapids:
5646 case TrackElemType::OnRidePhoto:
5647 case TrackElemType::Down25LeftBanked:
5648 case TrackElemType::Down25RightBanked:
5649 case TrackElemType::Whirlpool:
5650 case TrackElemType::ReverseFreefallVertical:
5651 case TrackElemType::Up90:
5652 case TrackElemType::Down90:
5653 case TrackElemType::DiagFlat:
5654 case TrackElemType::DiagUp25:
5655 case TrackElemType::DiagUp60:
5656 case TrackElemType::DiagDown25:
5657 case TrackElemType::DiagDown60:
5658 case TrackElemType::DiagFlatToLeftBank:
5659 case TrackElemType::DiagFlatToRightBank:
5660 case TrackElemType::DiagLeftBankToFlat:
5661 case TrackElemType::DiagRightBankToFlat:
5662 case TrackElemType::DiagLeftBank:
5663 case TrackElemType::DiagRightBank:
5664 case TrackElemType::LogFlumeReverser:
5665 case TrackElemType::SpinningTunnel:
5666 case TrackElemType::PoweredLift:
5667 case TrackElemType::MinigolfHoleA:
5668 case TrackElemType::MinigolfHoleB:
5669 case TrackElemType::MinigolfHoleC:
5670 case TrackElemType::MinigolfHoleD:
5671 case TrackElemType::MinigolfHoleE:
5672 case TrackElemType::LeftReverser:
5673 case TrackElemType::RightReverser:
5674 case TrackElemType::AirThrustVerticalDown:
5675 case TrackElemType::BlockBrakes:
5676 case TrackElemType::Up25ToLeftBankedUp25:
5677 case TrackElemType::Up25ToRightBankedUp25:
5678 case TrackElemType::LeftBankedUp25ToUp25:
5679 case TrackElemType::RightBankedUp25ToUp25:
5680 case TrackElemType::Down25ToLeftBankedDown25:
5681 case TrackElemType::Down25ToRightBankedDown25:
5682 case TrackElemType::LeftBankedDown25ToDown25:
5683 case TrackElemType::RightBankedDown25ToDown25:
5684 case TrackElemType::LeftQuarterTurn1TileUp90:
5685 case TrackElemType::RightQuarterTurn1TileUp90:
5686 case TrackElemType::LeftQuarterTurn1TileDown90:
5687 case TrackElemType::RightQuarterTurn1TileDown90:
5688 // 6d73FF
5689 // Do nothing
5690 break;
5691 case TrackElemType::FlatToUp25: //
5692 case TrackElemType::Down25ToFlat: //
5693 case TrackElemType::LeftBankToUp25:
5694 case TrackElemType::RightBankToUp25:
5695 case TrackElemType::Down25ToLeftBank:
5696 case TrackElemType::Down25ToRightBank:
5697 case TrackElemType::FlatToUp25Covered:
5698 case TrackElemType::Down25ToFlatCovered:
5699 case TrackElemType::LeftBankedFlatToLeftBankedUp25:
5700 case TrackElemType::RightBankedFlatToRightBankedUp25:
5701 case TrackElemType::LeftBankedDown25ToLeftBankedFlat:
5702 case TrackElemType::RightBankedDown25ToRightBankedFlat:
5703 case TrackElemType::FlatToLeftBankedUp25:
5704 case TrackElemType::FlatToRightBankedUp25:
5705 case TrackElemType::LeftBankedDown25ToFlat:
5706 case TrackElemType::RightBankedDown25ToFlat:
5707 vertFactor = 103;
5708 // 6d7509
5709 break;
5710 case TrackElemType::Up25ToFlat: //
5711 case TrackElemType::FlatToDown25: //
5712 case TrackElemType::Up25ToLeftBank:
5713 case TrackElemType::Up25ToRightBank:
5714 case TrackElemType::LeftBankToDown25:
5715 case TrackElemType::RightBankToDown25:
5716 case TrackElemType::Up25ToFlatCovered:
5717 case TrackElemType::FlatToDown25Covered:
5718 case TrackElemType::CableLiftHill:
5719 case TrackElemType::LeftBankedUp25ToLeftBankedFlat:
5720 case TrackElemType::RightBankedUp25ToRightBankedFlat:
5721 case TrackElemType::LeftBankedFlatToLeftBankedDown25:
5722 case TrackElemType::RightBankedFlatToRightBankedDown25:
5723 case TrackElemType::LeftBankedUp25ToFlat:
5724 case TrackElemType::RightBankedUp25ToFlat:
5725 case TrackElemType::FlatToLeftBankedDown25:
5726 case TrackElemType::FlatToRightBankedDown25:
5727 vertFactor = -103;
5728 // 6d7569
5729 break;
5730 case TrackElemType::Up25ToUp60: //
5731 case TrackElemType::Down60ToDown25: //
5732 case TrackElemType::Up25ToUp60Covered:
5733 case TrackElemType::Down60ToDown25Covered:
5734 vertFactor = 82;
5735 // 6d7545
5736 break;
5737 case TrackElemType::Up60ToUp25: //
5738 case TrackElemType::Down25ToDown60: //
5739 case TrackElemType::Up60ToUp25Covered:
5740 case TrackElemType::Down25ToDown60Covered:
5741 vertFactor = -82;
5742 // 6d7551
5743 break;
5744 case TrackElemType::LeftQuarterTurn5Tiles: //
5745 case TrackElemType::LeftQuarterTurn5TilesUp25:
5746 case TrackElemType::LeftQuarterTurn5TilesDown25:
5747 case TrackElemType::LeftTwistDownToUp:
5748 case TrackElemType::LeftTwistUpToDown:
5749 case TrackElemType::LeftQuarterTurn5TilesCovered:
5750 case TrackElemType::LeftQuarterHelixLargeUp:
5751 case TrackElemType::LeftQuarterHelixLargeDown:
5752 case TrackElemType::LeftFlyerTwistUp:
5753 case TrackElemType::LeftFlyerTwistDown:
5754 case TrackElemType::LeftHeartLineRoll:
5755 lateralFactor = 98;
5756 // 6d7590
5757 break;
5758 case TrackElemType::RightQuarterTurn5Tiles: //
5759 case TrackElemType::RightQuarterTurn5TilesUp25:
5760 case TrackElemType::RightQuarterTurn5TilesDown25:
5761 case TrackElemType::RightTwistDownToUp:
5762 case TrackElemType::RightTwistUpToDown:
5763 case TrackElemType::RightQuarterTurn5TilesCovered:
5764 case TrackElemType::RightQuarterHelixLargeUp:
5765 case TrackElemType::RightQuarterHelixLargeDown:
5766 case TrackElemType::RightFlyerTwistUp:
5767 case TrackElemType::RightFlyerTwistDown:
5768 case TrackElemType::RightHeartLineRoll:
5769 lateralFactor = -98;
5770 // 6d75B7
5771 break;
5772 case TrackElemType::BankedLeftQuarterTurn5Tiles:
5773 case TrackElemType::LeftHalfBankedHelixUpLarge:
5774 case TrackElemType::LeftHalfBankedHelixDownLarge:
5775 case TrackElemType::LeftQuarterBankedHelixLargeUp:
5776 case TrackElemType::LeftQuarterBankedHelixLargeDown:
5777 vertFactor = 200;
5778 lateralFactor = 160;
5779 // 6d75E1
5780 break;
5781 case TrackElemType::BankedRightQuarterTurn5Tiles:
5782 case TrackElemType::RightHalfBankedHelixUpLarge:
5783 case TrackElemType::RightHalfBankedHelixDownLarge:
5784 case TrackElemType::RightQuarterBankedHelixLargeUp:
5785 case TrackElemType::RightQuarterBankedHelixLargeDown:
5786 vertFactor = 200;
5787 lateralFactor = -160;
5788 // 6d75F0
5789 break;
5790 case TrackElemType::SBendLeft:
5791 case TrackElemType::SBendLeftCovered:
5792 lateralFactor = (track_progress < 48) ? 98 : -98;
5793 // 6d75FF
5794 break;
5795 case TrackElemType::SBendRight:
5796 case TrackElemType::SBendRightCovered:
5797 lateralFactor = (track_progress < 48) ? -98 : 98;
5798 // 6d7608
5799 break;
5800 case TrackElemType::LeftVerticalLoop:
5801 case TrackElemType::RightVerticalLoop:
5802 vertFactor = (abs(track_progress - 155) / 2) + 28;
5803 // 6d7690
5804 break;
5805 case TrackElemType::LeftQuarterTurn3Tiles:
5806 case TrackElemType::LeftQuarterTurn3TilesUp25:
5807 case TrackElemType::LeftQuarterTurn3TilesDown25:
5808 case TrackElemType::LeftQuarterTurn3TilesCovered:
5809 case TrackElemType::LeftCurvedLiftHill:
5810 lateralFactor = 59;
5811 // 6d7704
5812 break;
5813 case TrackElemType::RightQuarterTurn3Tiles:
5814 case TrackElemType::RightQuarterTurn3TilesUp25:
5815 case TrackElemType::RightQuarterTurn3TilesDown25:
5816 case TrackElemType::RightQuarterTurn3TilesCovered:
5817 case TrackElemType::RightCurvedLiftHill:
5818 lateralFactor = -59;
5819 // 6d7710
5820 break;
5821 case TrackElemType::LeftBankedQuarterTurn3Tiles:
5822 case TrackElemType::LeftHalfBankedHelixUpSmall:
5823 case TrackElemType::LeftHalfBankedHelixDownSmall:
5824 vertFactor = 100;
5825 lateralFactor = 100;
5826 // 6d7782
5827 break;
5828 case TrackElemType::RightBankedQuarterTurn3Tiles:
5829 case TrackElemType::RightHalfBankedHelixUpSmall:
5830 case TrackElemType::RightHalfBankedHelixDownSmall:
5831 vertFactor = 100;
5832 lateralFactor = -100;
5833 // 6d778E
5834 break;
5835 case TrackElemType::LeftQuarterTurn1Tile:
5836 lateralFactor = 45;
5837 // 6d779A
5838 break;
5839 case TrackElemType::RightQuarterTurn1Tile:
5840 lateralFactor = -45;
5841 // 6d77A3
5842 break;
5843 case TrackElemType::HalfLoopUp:
5844 case TrackElemType::FlyerHalfLoopUp:
5845 vertFactor = ((static_cast<uint16_t>(-(track_progress - 155))) / 2) + 28;
5846 // 6d763E
5847 break;
5848 case TrackElemType::HalfLoopDown:
5849 case TrackElemType::FlyerHalfLoopDown:
5850 vertFactor = (track_progress / 2) + 28;
5851 // 6d7656
5852 break;
5853 case TrackElemType::LeftCorkscrewUp:
5854 case TrackElemType::RightCorkscrewDown:
5855 case TrackElemType::LeftFlyerCorkscrewUp:
5856 case TrackElemType::RightFlyerCorkscrewDown:
5857 vertFactor = 52;
5858 lateralFactor = 70;
5859 // 6d76AA
5860 break;
5861 case TrackElemType::RightCorkscrewUp:
5862 case TrackElemType::LeftCorkscrewDown:
5863 case TrackElemType::RightFlyerCorkscrewUp:
5864 case TrackElemType::LeftFlyerCorkscrewDown:
5865 vertFactor = 52;
5866 lateralFactor = -70;
5867 // 6d76B9
5868 break;
5869 case TrackElemType::FlatToUp60:
5870 case TrackElemType::Down60ToFlat:
5871 vertFactor = 56;
5872 // 6d747C
5873 break;
5874 case TrackElemType::Up60ToFlat:
5875 case TrackElemType::FlatToDown60:
5876 case TrackElemType::BrakeForDrop:
5877 vertFactor = -56;
5878 // 6d7488
5879 break;
5880 case TrackElemType::LeftQuarterTurn1TileUp60:
5881 case TrackElemType::LeftQuarterTurn1TileDown60:
5882 lateralFactor = 88;
5883 // 6d7770
5884 break;
5885 case TrackElemType::RightQuarterTurn1TileUp60:
5886 case TrackElemType::RightQuarterTurn1TileDown60:
5887 lateralFactor = -88;
5888 // 6d7779
5889 break;
5890 case TrackElemType::Watersplash:
5891 vertFactor = -150;
5892 if (track_progress < 32)
5893 break;
5894 vertFactor = 150;
5895 if (track_progress < 64)
5896 break;
5897 vertFactor = 0;
5898 if (track_progress < 96)
5899 break;
5900 vertFactor = 150;
5901 if (track_progress < 128)
5902 break;
5903 vertFactor = -150;
5904 // 6d7408
5905 break;
5906 case TrackElemType::FlatToUp60LongBase:
5907 case TrackElemType::Down60ToFlatLongBase:
5908 vertFactor = 160;
5909 // 6d74F1
5910 break;
5911 case TrackElemType::Up60ToFlatLongBase:
5912 case TrackElemType::FlatToDown60LongBase:
5913 vertFactor = -160;
5914 // 6d74FD
5915 break;
5916 case TrackElemType::ReverseFreefallSlope:
5917 case TrackElemType::AirThrustVerticalDownToLevel:
5918 vertFactor = 120;
5919 // 6d7458
5920 break;
5921 case TrackElemType::Up60ToUp90:
5922 case TrackElemType::Down90ToDown60:
5923 vertFactor = 110;
5924 // 6d7515
5925 break;
5926 case TrackElemType::Up90ToUp60:
5927 case TrackElemType::Down60ToDown90:
5928 vertFactor = -110;
5929 // 6d7521
5930 break;
5931 case TrackElemType::LeftEighthToDiag:
5932 case TrackElemType::LeftEighthToOrthogonal:
5933 lateralFactor = 137;
5934 // 6d7575
5935 break;
5936 case TrackElemType::RightEighthToDiag:
5937 case TrackElemType::RightEighthToOrthogonal:
5938 lateralFactor = -137;
5939 // 6d759C
5940 break;
5941 case TrackElemType::LeftEighthBankToDiag:
5942 case TrackElemType::LeftEighthBankToOrthogonal:
5943 vertFactor = 270;
5944 lateralFactor = 200;
5945 // 6d75C3
5946 break;
5947 case TrackElemType::RightEighthBankToDiag:
5948 case TrackElemType::RightEighthBankToOrthogonal:
5949 vertFactor = 270;
5950 lateralFactor = -200;
5951 // 6d75D2
5952 break;
5953 case TrackElemType::DiagFlatToUp25:
5954 case TrackElemType::DiagDown25ToFlat:
5955 case TrackElemType::DiagLeftBankToUp25:
5956 case TrackElemType::DiagRightBankToUp25:
5957 case TrackElemType::DiagDown25ToLeftBank:
5958 case TrackElemType::DiagDown25ToRightBank:
5959 vertFactor = 113;
5960 // 6d7494
5961 break;
5962 case TrackElemType::DiagUp25ToFlat:
5963 case TrackElemType::DiagFlatToDown25:
5964 case TrackElemType::DiagUp25ToLeftBank:
5965 case TrackElemType::DiagUp25ToRightBank:
5966 case TrackElemType::DiagLeftBankToDown25:
5967 case TrackElemType::DiagRightBankToDown25:
5968 vertFactor = -113;
5969 // 6d755D
5970 break;
5971 case TrackElemType::DiagUp25ToUp60:
5972 case TrackElemType::DiagDown60ToDown25:
5973 vertFactor = 95;
5974 // 6D752D
5975 break;
5976 case TrackElemType::DiagUp60ToUp25:
5977 case TrackElemType::DiagDown25ToDown60:
5978 vertFactor = -95;
5979 // 6D7539
5980 break;
5981 case TrackElemType::DiagFlatToUp60:
5982 case TrackElemType::DiagDown60ToFlat:
5983 vertFactor = 60;
5984 // 6D7464
5985 break;
5986 case TrackElemType::DiagUp60ToFlat:
5987 case TrackElemType::DiagFlatToDown60:
5988 vertFactor = -60;
5989 // 6d7470
5990 break;
5991 case TrackElemType::LeftBarrelRollUpToDown:
5992 case TrackElemType::LeftBarrelRollDownToUp:
5993 vertFactor = 170;
5994 lateralFactor = 115;
5995 // 6d7581
5996 break;
5997 case TrackElemType::RightBarrelRollUpToDown:
5998 case TrackElemType::RightBarrelRollDownToUp:
5999 vertFactor = 170;
6000 lateralFactor = -115;
6001 // 6d75A8
6002 break;
6003 case TrackElemType::LeftBankToLeftQuarterTurn3TilesUp25:
6004 vertFactor = -(track_progress / 2) + 134;
6005 lateralFactor = 90;
6006 // 6d771C
6007 break;
6008 case TrackElemType::RightBankToRightQuarterTurn3TilesUp25:
6009 vertFactor = -(track_progress / 2) + 134;
6010 lateralFactor = -90;
6011 // 6D7746
6012 break;
6013 case TrackElemType::LeftQuarterTurn3TilesDown25ToLeftBank:
6014 vertFactor = -(track_progress / 2) + 134;
6015 lateralFactor = 90;
6016 // 6D7731 identical to 6d771c
6017 break;
6018 case TrackElemType::RightQuarterTurn3TilesDown25ToRightBank:
6019 vertFactor = -(track_progress / 2) + 134;
6020 lateralFactor = -90;
6021 // 6D775B identical to 6d7746
6022 break;
6023 case TrackElemType::LeftLargeHalfLoopUp:
6024 case TrackElemType::RightLargeHalfLoopUp:
6025 vertFactor = ((static_cast<uint16_t>(-(track_progress - 311))) / 4) + 46;
6026 // 6d7666
6027 break;
6028 case TrackElemType::RightLargeHalfLoopDown:
6029 case TrackElemType::LeftLargeHalfLoopDown:
6030 vertFactor = (track_progress / 4) + 46;
6031 // 6d767F
6032 break;
6033 case TrackElemType::HeartLineTransferUp:
6034 vertFactor = 103;
6035 if (track_progress < 32)
6036 break;
6037 vertFactor = -103;
6038 if (track_progress < 64)
6039 break;
6040 vertFactor = 0;
6041 if (track_progress < 96)
6042 break;
6043 vertFactor = 103;
6044 if (track_progress < 128)
6045 break;
6046 vertFactor = -103;
6047 // 6d74A0
6048 break;
6049 case TrackElemType::HeartLineTransferDown:
6050 vertFactor = -103;
6051 if (track_progress < 32)
6052 break;
6053 vertFactor = 103;
6054 if (track_progress < 64)
6055 break;
6056 vertFactor = 0;
6057 if (track_progress < 96)
6058 break;
6059 vertFactor = -103;
6060 if (track_progress < 128)
6061 break;
6062 vertFactor = 103;
6063 // 6D74CA
6064 break;
6065 case TrackElemType::MultiDimInvertedFlatToDown90QuarterLoop:
6066 case TrackElemType::InvertedFlatToDown90QuarterLoop:
6067 case TrackElemType::MultiDimFlatToDown90QuarterLoop:
6068 vertFactor = (track_progress / 4) + 55;
6069 // 6d762D
6070 break;
6071 case TrackElemType::Up90ToInvertedFlatQuarterLoop:
6072 case TrackElemType::MultiDimUp90ToInvertedFlatQuarterLoop:
6073 case TrackElemType::MultiDimInvertedUp90ToFlatQuarterLoop:
6074 vertFactor = ((static_cast<uint16_t>(-(track_progress - 137))) / 4) + 55;
6075 // 6D7614
6076 break;
6077 case TrackElemType::AirThrustTopCap:
6078 vertFactor = -60;
6079 // 6D744C
6080 break;
6081 case TrackElemType::LeftBankedQuarterTurn3TileUp25:
6082 case TrackElemType::LeftBankedQuarterTurn3TileDown25:
6083 vertFactor = 200;
6084 lateralFactor = 100;
6085 // 6d76C8
6086 break;
6087 case TrackElemType::RightBankedQuarterTurn3TileUp25:
6088 case TrackElemType::RightBankedQuarterTurn3TileDown25:
6089 vertFactor = 200;
6090 lateralFactor = -100;
6091 // 6d76d7
6092 break;
6093 case TrackElemType::LeftBankedQuarterTurn5TileUp25:
6094 case TrackElemType::LeftBankedQuarterTurn5TileDown25:
6095 vertFactor = 200;
6096 lateralFactor = 160;
6097 // 6D76E6
6098 break;
6099 case TrackElemType::RightBankedQuarterTurn5TileUp25:
6100 case TrackElemType::RightBankedQuarterTurn5TileDown25:
6101 vertFactor = 200;
6102 lateralFactor = -160;
6103 // 6d76F5
6104 break;
6105 }
6106
6107 int32_t gForceLateral = 0;
6108
6109 if (vertFactor != 0)
6110 {
6111 gForceVert += abs(velocity) * 98 / vertFactor;
6112 }
6113
6114 if (lateralFactor != 0)
6115 {
6116 gForceLateral += abs(velocity) * 98 / lateralFactor;
6117 }
6118
6119 gForceVert *= 10;
6120 gForceLateral *= 10;
6121 gForceVert >>= 16;
6122 gForceLateral >>= 16;
6123 return { static_cast<int16_t>(gForceVert & 0xFFFF), static_cast<int16_t>(gForceLateral & 0xFFFF) };
6124 }
6125
SetMapToolbar() const6126 void Vehicle::SetMapToolbar() const
6127 {
6128 auto curRide = GetRide();
6129 if (curRide != nullptr && curRide->type < RIDE_TYPE_COUNT)
6130 {
6131 const Vehicle* vehicle = GetHead();
6132
6133 int32_t vehicleIndex;
6134 for (vehicleIndex = 0; vehicleIndex < 32; vehicleIndex++)
6135 if (curRide->vehicles[vehicleIndex] == vehicle->sprite_index)
6136 break;
6137
6138 auto ft = Formatter();
6139 ft.Add<rct_string_id>(STR_RIDE_MAP_TIP);
6140 ft.Add<rct_string_id>(STR_MAP_TOOLTIP_STRINGID_STRINGID);
6141 curRide->FormatNameTo(ft);
6142 ft.Add<rct_string_id>(GetRideComponentName(GetRideTypeDescriptor(curRide->type).NameConvention.vehicle).capitalised);
6143 ft.Add<uint16_t>(vehicleIndex + 1);
6144 curRide->FormatStatusTo(ft);
6145 auto intent = Intent(INTENT_ACTION_SET_MAP_TOOLTIP);
6146 intent.putExtra(INTENT_EXTRA_FORMATTER, &ft);
6147 context_broadcast_intent(&intent);
6148 }
6149 }
6150
TrainHead() const6151 Vehicle* Vehicle::TrainHead() const
6152 {
6153 const Vehicle* vehicle = this;
6154 Vehicle* prevVehicle;
6155
6156 for (;;)
6157 {
6158 prevVehicle = GetEntity<Vehicle>(vehicle->prev_vehicle_on_ride);
6159 if (prevVehicle == nullptr)
6160 return nullptr;
6161 if (prevVehicle->next_vehicle_on_train == SPRITE_INDEX_NULL)
6162 break;
6163
6164 vehicle = prevVehicle;
6165 }
6166
6167 return const_cast<Vehicle*>(vehicle);
6168 }
6169
TrainTail() const6170 Vehicle* Vehicle::TrainTail() const
6171 {
6172 const Vehicle* vehicle = this;
6173 uint16_t spriteIndex;
6174
6175 while ((spriteIndex = vehicle->next_vehicle_on_train) != SPRITE_INDEX_NULL)
6176 {
6177 vehicle = GetEntity<Vehicle>(spriteIndex);
6178 if (vehicle == nullptr)
6179 {
6180 return const_cast<Vehicle*>(this);
6181 }
6182 }
6183
6184 return const_cast<Vehicle*>(vehicle);
6185 }
6186
IsUsedInPairs() const6187 int32_t Vehicle::IsUsedInPairs() const
6188 {
6189 return num_seats & VEHICLE_SEAT_PAIR_FLAG;
6190 }
6191
6192 /**
6193 *
6194 * rct2: 0x006DA44E
6195 */
UpdateMotionDodgems()6196 int32_t Vehicle::UpdateMotionDodgems()
6197 {
6198 _vehicleMotionTrackFlags = 0;
6199
6200 auto curRide = GetRide();
6201 if (curRide == nullptr)
6202 return _vehicleMotionTrackFlags;
6203
6204 int32_t nextVelocity = velocity + acceleration;
6205 if (curRide->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN)
6206 && curRide->breakdown_reason_pending == BREAKDOWN_SAFETY_CUT_OUT)
6207 {
6208 nextVelocity = 0;
6209 }
6210 velocity = nextVelocity;
6211
6212 _vehicleVelocityF64E08 = nextVelocity;
6213 _vehicleVelocityF64E0C = (nextVelocity / 1024) * 42;
6214 _vehicleUnkF64E10 = 1;
6215
6216 acceleration = 0;
6217 if (!(curRide->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN))
6218 || curRide->breakdown_reason_pending != BREAKDOWN_SAFETY_CUT_OUT)
6219 {
6220 if (gCurrentTicks & 1 && var_34 != 0)
6221 {
6222 if (var_34 > 0)
6223 {
6224 var_34--;
6225 sprite_direction += 2;
6226 }
6227 else
6228 {
6229 var_34++;
6230 sprite_direction -= 2;
6231 }
6232 sprite_direction &= 0x1E;
6233 Invalidate();
6234 }
6235 else if ((scenario_rand() & 0xFFFF) <= 2849)
6236 {
6237 if (var_35 & (1 << 6))
6238 sprite_direction -= 2;
6239 else
6240 sprite_direction += 2;
6241 sprite_direction &= 0x1E;
6242 Invalidate();
6243 }
6244 }
6245
6246 uint16_t collideSprite = SPRITE_INDEX_NULL;
6247
6248 if (dodgems_collision_direction != 0)
6249 {
6250 uint8_t oldCollisionDirection = dodgems_collision_direction & 0x1E;
6251 dodgems_collision_direction = 0;
6252
6253 CoordsXYZ location = { x, y, z };
6254
6255 location.x += Unk9A36C4[oldCollisionDirection].x;
6256 location.y += Unk9A36C4[oldCollisionDirection].y;
6257 location.x += Unk9A36C4[oldCollisionDirection + 1].x;
6258 location.y += Unk9A36C4[oldCollisionDirection + 1].y;
6259
6260 if (!DodgemsCarWouldCollideAt(location, &collideSprite))
6261 {
6262 MoveTo(location);
6263 }
6264 }
6265
6266 remaining_distance += _vehicleVelocityF64E0C;
6267
6268 if (remaining_distance >= 13962)
6269 {
6270 sound2_flags &= ~VEHICLE_SOUND2_FLAGS_LIFT_HILL;
6271 unk_F64E20.x = x;
6272 unk_F64E20.y = y;
6273 unk_F64E20.z = z;
6274
6275 while (true)
6276 {
6277 var_35++;
6278 uint8_t direction = sprite_direction;
6279 direction |= var_35 & 1;
6280
6281 CoordsXY location = unk_F64E20;
6282 location.x += Unk9A36C4[direction].x;
6283 location.y += Unk9A36C4[direction].y;
6284
6285 if (DodgemsCarWouldCollideAt(location, &collideSprite))
6286 break;
6287
6288 remaining_distance -= Unk9A36C4[direction].distance;
6289 unk_F64E20.x = location.x;
6290 unk_F64E20.y = location.y;
6291 if (remaining_distance < 13962)
6292 {
6293 break;
6294 }
6295 _vehicleUnkF64E10++;
6296 }
6297
6298 if (remaining_distance >= 13962)
6299 {
6300 int32_t oldVelocity = velocity;
6301 remaining_distance = 0;
6302 velocity = 0;
6303 uint8_t direction = sprite_direction | 1;
6304
6305 Vehicle* collideVehicle = GetEntity<Vehicle>(collideSprite);
6306 if (collideVehicle != nullptr)
6307 {
6308 var_34 = (scenario_rand() & 1) ? 1 : -1;
6309
6310 if (oldVelocity >= 131072)
6311 {
6312 collideVehicle->dodgems_collision_direction = direction;
6313 dodgems_collision_direction = direction ^ (1 << 4);
6314 }
6315 }
6316 else
6317 {
6318 var_34 = (scenario_rand() & 1) ? 6 : -6;
6319
6320 if (oldVelocity >= 131072)
6321 {
6322 dodgems_collision_direction = direction ^ (1 << 4);
6323 }
6324 }
6325 }
6326
6327 MoveTo(unk_F64E20);
6328 }
6329
6330 int32_t eax = velocity / 2;
6331 int32_t edx = velocity >> 8;
6332 edx *= edx;
6333 if (velocity < 0)
6334 edx = -edx;
6335 edx >>= 5;
6336 eax += edx;
6337 if (mass != 0)
6338 {
6339 eax /= mass;
6340 }
6341 rct_ride_entry* rideEntry = GetRideEntry();
6342 rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle_type];
6343
6344 if (!(vehicleEntry->flags & VEHICLE_ENTRY_FLAG_POWERED))
6345 {
6346 acceleration = -eax;
6347 return _vehicleMotionTrackFlags;
6348 }
6349
6350 int32_t momentum = (speed * mass) >> 2;
6351 int32_t _eax = speed << 14;
6352 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_REVERSING_SHUTTLE))
6353 {
6354 _eax = -_eax;
6355 }
6356 _eax -= velocity;
6357 _eax *= powered_acceleration * 2;
6358 if (momentum != 0)
6359 _eax /= momentum;
6360
6361 acceleration = _eax - eax;
6362 return _vehicleMotionTrackFlags;
6363 }
6364
6365 /**
6366 *
6367 * rct2: 0x006DD365
6368 */
wouldCollideWithDodgemsTrackEdge(const CoordsXY & coords,const CoordsXY & trackLocation,uint32_t trackType,uint16_t dodgemsCarRadius)6369 static bool wouldCollideWithDodgemsTrackEdge(
6370 const CoordsXY& coords, const CoordsXY& trackLocation, uint32_t trackType, uint16_t dodgemsCarRadius)
6371 {
6372 int16_t rideLeft = trackLocation.x + DodgemsTrackSize(trackType).left;
6373 int16_t rideRight = trackLocation.x + DodgemsTrackSize(trackType).right;
6374 int16_t rideTop = trackLocation.y + DodgemsTrackSize(trackType).top;
6375 int16_t rideBottom = trackLocation.y + DodgemsTrackSize(trackType).bottom;
6376
6377 return coords.x - dodgemsCarRadius < rideLeft || coords.y - dodgemsCarRadius < rideTop
6378 || coords.x + dodgemsCarRadius > rideRight || coords.y + dodgemsCarRadius > rideBottom;
6379 }
6380
DodgemsCarWouldCollideAt(const CoordsXY & coords,uint16_t * collidedWith) const6381 bool Vehicle::DodgemsCarWouldCollideAt(const CoordsXY& coords, uint16_t* collidedWith) const
6382 {
6383 auto trackType = GetTrackType();
6384
6385 if (wouldCollideWithDodgemsTrackEdge(coords, TrackLocation, trackType, (var_44 * 30) >> 9))
6386 {
6387 if (collidedWith != nullptr)
6388 *collidedWith = SPRITE_INDEX_NULL;
6389 return true;
6390 }
6391
6392 auto location = coords;
6393
6394 ride_id_t rideIndex = ride;
6395 for (auto xy_offset : SurroundingTiles)
6396 {
6397 location += xy_offset;
6398
6399 for (auto vehicle2 : EntityTileList<Vehicle>(location))
6400 {
6401 if (vehicle2 == this)
6402 continue;
6403 if (vehicle2->ride != rideIndex)
6404 continue;
6405
6406 int32_t distX = abs(coords.x - vehicle2->x);
6407 if (distX > 32768)
6408 continue;
6409
6410 int32_t distY = abs(coords.y - vehicle2->y);
6411 if (distY > 32768)
6412 continue;
6413
6414 int32_t ecx = (var_44 + vehicle2->var_44) / 2;
6415 ecx *= 30;
6416 ecx >>= 8;
6417 if (std::max(distX, distY) < ecx)
6418 {
6419 if (collidedWith != nullptr)
6420 *collidedWith = vehicle2->sprite_index;
6421 return true;
6422 }
6423 }
6424 }
6425
6426 return false;
6427 }
6428
6429 /**
6430 *
6431 * rct2: 0x006DAB90
6432 */
UpdateTrackMotionUpStopCheck() const6433 void Vehicle::UpdateTrackMotionUpStopCheck() const
6434 {
6435 auto vehicleEntry = Entry();
6436 if (vehicleEntry == nullptr)
6437 {
6438 return;
6439 }
6440
6441 // No up stops (coaster types)
6442 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_NO_UPSTOP_WHEELS)
6443 {
6444 auto trackType = GetTrackType();
6445 if (!track_element_is_covered(trackType))
6446 {
6447 auto gForces = GetGForces();
6448 gForces.LateralG = std::abs(gForces.LateralG);
6449 if (gForces.LateralG <= 150)
6450 {
6451 if (dword_9A2970[Pitch] < 0)
6452 {
6453 if (gForces.VerticalG > -40)
6454 {
6455 return;
6456 }
6457 }
6458 else if (gForces.VerticalG > -80)
6459 {
6460 return;
6461 }
6462 }
6463
6464 if (Pitch != 8)
6465 {
6466 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_DERAILED;
6467 }
6468 }
6469 }
6470 else if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_NO_UPSTOP_BOBSLEIGH)
6471 {
6472 // No up stops bobsleigh type
6473 auto trackType = GetTrackType();
6474 if (!track_element_is_covered(trackType))
6475 {
6476 auto gForces = GetGForces();
6477
6478 if (dword_9A2970[Pitch] < 0)
6479 {
6480 if (gForces.VerticalG > -45)
6481 {
6482 return;
6483 }
6484 }
6485 else
6486 {
6487 if (gForces.VerticalG > -80)
6488 {
6489 return;
6490 }
6491 }
6492
6493 if (Pitch != 8 && Pitch != 55)
6494 {
6495 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_DERAILED;
6496 }
6497 }
6498 }
6499 }
6500
6501 /**
6502 * Modifies the train's velocity to match the block-brake fixed velocity.
6503 * This function must be called when the car is running through a non-stopping
6504 * state block-brake (precondition), which means that the block brake is acting
6505 * merely as a velocity regulator, in a closed state. When the brake is open, it
6506 * boosts the train to the speed limit
6507 */
ApplyNonStopBlockBrake()6508 void Vehicle::ApplyNonStopBlockBrake()
6509 {
6510 if (velocity >= 0)
6511 {
6512 // If the vehicle is below the speed limit
6513 if (velocity <= BLOCK_BRAKE_BASE_SPEED)
6514 {
6515 // Boost it to the fixed block brake speed
6516 velocity = BLOCK_BRAKE_BASE_SPEED;
6517 acceleration = 0;
6518 }
6519 else
6520 {
6521 // Slow it down till the fixed block brake speed
6522 velocity -= velocity >> 4;
6523 acceleration = 0;
6524 }
6525 }
6526 }
6527
6528 /**
6529 *
6530 * Modifies the train's velocity influenced by a block brake
6531 */
ApplyStopBlockBrake()6532 void Vehicle::ApplyStopBlockBrake()
6533 {
6534 // Slow it down till completely stop the car
6535 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_BLOCK_BRAKE;
6536 acceleration = 0;
6537 // If the this is slow enough, stop it. If not, slow it down
6538 if (velocity <= 0x20000)
6539 {
6540 velocity = 0;
6541 }
6542 else
6543 {
6544 velocity -= velocity >> 3;
6545 }
6546 }
6547
6548 /**
6549 *
6550 * rct2: 0x006DAC43
6551 */
CheckAndApplyBlockSectionStopSite()6552 void Vehicle::CheckAndApplyBlockSectionStopSite()
6553 {
6554 auto curRide = GetRide();
6555 if (curRide == nullptr)
6556 return;
6557
6558 auto vehicleEntry = Entry();
6559 if (vehicleEntry == nullptr)
6560 return;
6561
6562 // Is chair lift type
6563 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_CHAIRLIFT)
6564 {
6565 velocity = _vehicleBreakdown == 0 ? 0 : curRide->speed << 16;
6566 acceleration = 0;
6567 }
6568
6569 auto trackType = GetTrackType();
6570
6571 TileElement* trackElement = map_get_track_element_at_of_type(TrackLocation, trackType);
6572
6573 if (trackElement == nullptr)
6574 {
6575 return;
6576 }
6577
6578 switch (trackType)
6579 {
6580 case TrackElemType::BlockBrakes:
6581 if (curRide->IsBlockSectioned() && trackElement->AsTrack()->BlockBrakeClosed())
6582 ApplyStopBlockBrake();
6583 else
6584 ApplyNonStopBlockBrake();
6585
6586 break;
6587 case TrackElemType::EndStation:
6588 if (trackElement->AsTrack()->BlockBrakeClosed())
6589 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_BLOCK_BRAKE;
6590
6591 break;
6592 case TrackElemType::Up25ToFlat:
6593 case TrackElemType::Up60ToFlat:
6594 case TrackElemType::CableLiftHill:
6595 case TrackElemType::DiagUp25ToFlat:
6596 case TrackElemType::DiagUp60ToFlat:
6597 if (curRide->IsBlockSectioned())
6598 {
6599 if (trackType == TrackElemType::CableLiftHill || trackElement->AsTrack()->HasChain())
6600 {
6601 if (trackElement->AsTrack()->BlockBrakeClosed())
6602 {
6603 ApplyStopBlockBrake();
6604 }
6605 }
6606 }
6607 break;
6608 }
6609 }
6610
6611 /**
6612 *
6613 * rct2: 0x006DADAE
6614 */
UpdateVelocity()6615 void Vehicle::UpdateVelocity()
6616 {
6617 int32_t nextVelocity = acceleration + velocity;
6618 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_ZERO_VELOCITY))
6619 {
6620 nextVelocity = 0;
6621 }
6622 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_ON_BRAKE_FOR_DROP))
6623 {
6624 vertical_drop_countdown--;
6625 if (vertical_drop_countdown == -70)
6626 {
6627 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_ON_BRAKE_FOR_DROP);
6628 }
6629 if (vertical_drop_countdown >= 0)
6630 {
6631 nextVelocity = 0;
6632 acceleration = 0;
6633 }
6634 }
6635 velocity = nextVelocity;
6636
6637 _vehicleVelocityF64E08 = nextVelocity;
6638 _vehicleVelocityF64E0C = (nextVelocity >> 10) * 42;
6639 }
6640
block_brakes_open_previous_section(Ride & ride,const CoordsXYZ & vehicleTrackLocation,TileElement * tileElement)6641 static void block_brakes_open_previous_section(Ride& ride, const CoordsXYZ& vehicleTrackLocation, TileElement* tileElement)
6642 {
6643 auto location = vehicleTrackLocation;
6644 track_begin_end trackBeginEnd, slowTrackBeginEnd;
6645 TileElement slowTileElement = *tileElement;
6646 bool counter = true;
6647 CoordsXY slowLocation = location;
6648 do
6649 {
6650 if (!track_block_get_previous({ location, tileElement }, &trackBeginEnd))
6651 {
6652 return;
6653 }
6654 if (trackBeginEnd.begin_x == vehicleTrackLocation.x && trackBeginEnd.begin_y == vehicleTrackLocation.y
6655 && tileElement == trackBeginEnd.begin_element)
6656 {
6657 return;
6658 }
6659
6660 location.x = trackBeginEnd.end_x;
6661 location.y = trackBeginEnd.end_y;
6662 location.z = trackBeginEnd.begin_z;
6663 tileElement = trackBeginEnd.begin_element;
6664
6665 //#2081: prevent infinite loop
6666 counter = !counter;
6667 if (counter)
6668 {
6669 track_block_get_previous({ slowLocation, &slowTileElement }, &slowTrackBeginEnd);
6670 slowLocation.x = slowTrackBeginEnd.end_x;
6671 slowLocation.y = slowTrackBeginEnd.end_y;
6672 slowTileElement = *(slowTrackBeginEnd.begin_element);
6673 if (slowLocation == location && slowTileElement.GetBaseZ() == tileElement->GetBaseZ()
6674 && slowTileElement.GetType() == tileElement->GetType()
6675 && slowTileElement.GetDirection() == tileElement->GetDirection())
6676 {
6677 return;
6678 }
6679 }
6680 } while (!(trackBeginEnd.begin_element->AsTrack()->IsBlockStart()));
6681
6682 // Get the start of the track block instead of the end
6683 location = { trackBeginEnd.begin_x, trackBeginEnd.begin_y, trackBeginEnd.begin_z };
6684 auto trackElement = map_get_track_element_at(location);
6685 if (trackElement == nullptr)
6686 {
6687 return;
6688 }
6689 trackElement->SetBlockBrakeClosed(false);
6690 map_invalidate_element(location, reinterpret_cast<TileElement*>(trackElement));
6691
6692 auto trackType = trackElement->GetTrackType();
6693 if (trackType == TrackElemType::BlockBrakes || trackType == TrackElemType::EndStation)
6694 {
6695 if (ride.IsBlockSectioned())
6696 {
6697 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::BlockBrakeClose, location);
6698 }
6699 }
6700 }
6701
GetSwingAmount() const6702 int32_t Vehicle::GetSwingAmount() const
6703 {
6704 auto trackType = GetTrackType();
6705 switch (trackType)
6706 {
6707 case TrackElemType::LeftQuarterTurn5Tiles:
6708 case TrackElemType::BankedLeftQuarterTurn5Tiles:
6709 case TrackElemType::LeftQuarterTurn5TilesUp25:
6710 case TrackElemType::LeftQuarterTurn5TilesDown25:
6711 case TrackElemType::LeftQuarterTurn5TilesCovered:
6712 case TrackElemType::LeftHalfBankedHelixUpLarge:
6713 case TrackElemType::LeftHalfBankedHelixDownLarge:
6714 case TrackElemType::LeftQuarterBankedHelixLargeUp:
6715 case TrackElemType::LeftQuarterBankedHelixLargeDown:
6716 case TrackElemType::LeftQuarterHelixLargeUp:
6717 case TrackElemType::LeftQuarterHelixLargeDown:
6718 case TrackElemType::LeftBankedQuarterTurn5TileUp25:
6719 case TrackElemType::LeftBankedQuarterTurn5TileDown25:
6720 // loc_6D67E1
6721 return 14;
6722
6723 case TrackElemType::RightQuarterTurn5Tiles:
6724 case TrackElemType::BankedRightQuarterTurn5Tiles:
6725 case TrackElemType::RightQuarterTurn5TilesUp25:
6726 case TrackElemType::RightQuarterTurn5TilesDown25:
6727 case TrackElemType::RightQuarterTurn5TilesCovered:
6728 case TrackElemType::RightHalfBankedHelixUpLarge:
6729 case TrackElemType::RightHalfBankedHelixDownLarge:
6730 case TrackElemType::RightQuarterBankedHelixLargeUp:
6731 case TrackElemType::RightQuarterBankedHelixLargeDown:
6732 case TrackElemType::RightQuarterHelixLargeUp:
6733 case TrackElemType::RightQuarterHelixLargeDown:
6734 case TrackElemType::RightBankedQuarterTurn5TileUp25:
6735 case TrackElemType::RightBankedQuarterTurn5TileDown25:
6736 // loc_6D6804
6737 return -14;
6738
6739 case TrackElemType::SBendLeft:
6740 case TrackElemType::SBendLeftCovered:
6741 // loc_6D67EF
6742 if (track_progress < 48)
6743 {
6744 return 14;
6745 }
6746 return -15;
6747
6748 case TrackElemType::SBendRight:
6749 case TrackElemType::SBendRightCovered:
6750 // loc_6D67CC
6751 if (track_progress < 48)
6752 {
6753 return -14;
6754 }
6755 return 15;
6756
6757 case TrackElemType::LeftQuarterTurn3Tiles:
6758 case TrackElemType::LeftBankedQuarterTurn3Tiles:
6759 case TrackElemType::LeftQuarterTurn3TilesUp25:
6760 case TrackElemType::LeftQuarterTurn3TilesDown25:
6761 case TrackElemType::LeftQuarterTurn3TilesCovered:
6762 case TrackElemType::LeftHalfBankedHelixUpSmall:
6763 case TrackElemType::LeftHalfBankedHelixDownSmall:
6764 case TrackElemType::LeftBankToLeftQuarterTurn3TilesUp25:
6765 case TrackElemType::LeftQuarterTurn3TilesDown25ToLeftBank:
6766 case TrackElemType::LeftCurvedLiftHill:
6767 case TrackElemType::LeftBankedQuarterTurn3TileUp25:
6768 case TrackElemType::LeftBankedQuarterTurn3TileDown25:
6769 // loc_6D67BE
6770 return 13;
6771
6772 case TrackElemType::RightQuarterTurn3Tiles:
6773 case TrackElemType::RightBankedQuarterTurn3Tiles:
6774 case TrackElemType::RightQuarterTurn3TilesUp25:
6775 case TrackElemType::RightQuarterTurn3TilesDown25:
6776 case TrackElemType::RightQuarterTurn3TilesCovered:
6777 case TrackElemType::RightHalfBankedHelixUpSmall:
6778 case TrackElemType::RightHalfBankedHelixDownSmall:
6779 case TrackElemType::RightBankToRightQuarterTurn3TilesUp25:
6780 case TrackElemType::RightQuarterTurn3TilesDown25ToRightBank:
6781 case TrackElemType::RightCurvedLiftHill:
6782 case TrackElemType::RightBankedQuarterTurn3TileUp25:
6783 case TrackElemType::RightBankedQuarterTurn3TileDown25:
6784 // loc_6D67B0
6785 return -13;
6786
6787 case TrackElemType::LeftQuarterTurn1Tile:
6788 case TrackElemType::LeftQuarterTurn1TileUp60:
6789 case TrackElemType::LeftQuarterTurn1TileDown60:
6790 // loc_6D67A2
6791 return 12;
6792
6793 case TrackElemType::RightQuarterTurn1Tile:
6794 case TrackElemType::RightQuarterTurn1TileUp60:
6795 case TrackElemType::RightQuarterTurn1TileDown60:
6796 // loc_6D6794
6797 return -12;
6798
6799 case TrackElemType::LeftEighthToDiag:
6800 case TrackElemType::LeftEighthToOrthogonal:
6801 case TrackElemType::LeftEighthBankToDiag:
6802 case TrackElemType::LeftEighthBankToOrthogonal:
6803 // loc_6D67D3
6804 return 15;
6805
6806 case TrackElemType::RightEighthToDiag:
6807 case TrackElemType::RightEighthToOrthogonal:
6808 case TrackElemType::RightEighthBankToDiag:
6809 case TrackElemType::RightEighthBankToOrthogonal:
6810 // loc_6D67F6
6811 return -15;
6812 }
6813 return 0;
6814 }
6815
GetSwingSprite(int16_t swingPosition)6816 static uint8_t GetSwingSprite(int16_t swingPosition)
6817 {
6818 if (swingPosition < -10012)
6819 return 11;
6820 if (swingPosition > 10012)
6821 return 12;
6822
6823 if (swingPosition < -8191)
6824 return 9;
6825 if (swingPosition > 8191)
6826 return 10;
6827
6828 if (swingPosition < -6371)
6829 return 7;
6830 if (swingPosition > 6371)
6831 return 8;
6832
6833 if (swingPosition < -4550)
6834 return 5;
6835 if (swingPosition > 4550)
6836 return 6;
6837
6838 if (swingPosition < -2730)
6839 return 3;
6840 if (swingPosition > 2730)
6841 return 4;
6842
6843 if (swingPosition < -910)
6844 return 1;
6845 if (swingPosition > 910)
6846 return 2;
6847
6848 return 0;
6849 }
6850
6851 /**
6852 *
6853 * rct2: 0x006D6776
6854 */
UpdateSwingingCar()6855 void Vehicle::UpdateSwingingCar()
6856 {
6857 int32_t dword_F64E08 = abs(_vehicleVelocityF64E08);
6858 SwingSpeed += (-SwingPosition) >> 6;
6859 int32_t swingAmount = GetSwingAmount();
6860 if (swingAmount < 0)
6861 {
6862 SwingSpeed -= dword_F64E08 >> (-swingAmount);
6863 }
6864 else if (swingAmount > 0)
6865 {
6866 SwingSpeed += dword_F64E08 >> swingAmount;
6867 }
6868
6869 auto vehicleEntry = Entry();
6870 if (vehicleEntry == nullptr)
6871 {
6872 return;
6873 }
6874 int16_t dx = 3185;
6875 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_SUSPENDED_SWING)
6876 {
6877 dx = 5006;
6878 }
6879 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_WOODEN_WILD_MOUSE_SWING)
6880 {
6881 dx = 1820;
6882 }
6883 int16_t cx = -dx;
6884
6885 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_SLIDE_SWING)
6886 {
6887 dx = 5370;
6888 cx = -5370;
6889
6890 auto trackType = GetTrackType();
6891 switch (trackType)
6892 {
6893 case TrackElemType::BankedLeftQuarterTurn5Tiles:
6894 case TrackElemType::LeftBank:
6895 case TrackElemType::LeftBankedQuarterTurn3Tiles:
6896 dx = 10831;
6897 cx = -819;
6898 break;
6899 case TrackElemType::BankedRightQuarterTurn5Tiles:
6900 case TrackElemType::RightBank:
6901 case TrackElemType::RightBankedQuarterTurn3Tiles:
6902 dx = 819;
6903 cx = -10831;
6904 break;
6905 }
6906
6907 if (track_type_is_station(trackType) || trackType == TrackElemType::Brakes || trackType == TrackElemType::BlockBrakes)
6908 {
6909 dx = 0;
6910 cx = 0;
6911 }
6912
6913 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL))
6914 {
6915 dx = 0;
6916 cx = 0;
6917 }
6918 }
6919
6920 SwingPosition += SwingSpeed;
6921 SwingSpeed -= SwingSpeed >> 5;
6922
6923 if (SwingPosition > dx)
6924 {
6925 SwingPosition = dx;
6926 SwingSpeed = 0;
6927 }
6928 if (SwingPosition < cx)
6929 {
6930 SwingPosition = cx;
6931 SwingSpeed = 0;
6932 }
6933
6934 uint8_t swingSprite = GetSwingSprite(SwingPosition);
6935
6936 if (swingSprite != SwingSprite)
6937 {
6938 SwingSprite = swingSprite;
6939 Invalidate();
6940 }
6941 }
6942
6943 /**
6944 *
6945 * rct2: 0x006D661F
6946 */
UpdateSpinningCar()6947 void Vehicle::UpdateSpinningCar()
6948 {
6949 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_ROTATION_OFF_WILD_MOUSE))
6950 {
6951 spin_speed = 0;
6952 return;
6953 }
6954
6955 auto vehicleEntry = Entry();
6956 if (vehicleEntry == nullptr)
6957 {
6958 return;
6959 }
6960 int32_t spinningInertia = vehicleEntry->spinning_inertia;
6961 auto trackType = GetTrackType();
6962 int32_t dword_F64E08 = _vehicleVelocityF64E08;
6963 int32_t spinSpeed{};
6964 // An L spin adds to the spin speed, R does the opposite
6965 // The number indicates how much right shift of the velocity will become spin
6966 // The bigger the number the less change in spin.
6967
6968 const auto& ted = GetTrackElementDescriptor(trackType);
6969 switch (ted.SpinFunction)
6970 {
6971 case RC_SPIN:
6972 // On a rotation control track element
6973 spinningInertia += 6;
6974 spinSpeed = dword_F64E08 >> spinningInertia;
6975 // Alternate the spin direction (roughly). Perhaps in future save a value to the track
6976 if (sprite_index & 1)
6977 {
6978 spin_speed -= spinSpeed;
6979 }
6980 else
6981 {
6982 spin_speed += spinSpeed;
6983 }
6984 break;
6985 case R5_SPIN:
6986 // It looks like in the original there was going to be special code for whirlpool
6987 // this has been removed and just uses R5_SPIN
6988 spinningInertia += 5;
6989 spin_speed -= dword_F64E08 >> spinningInertia;
6990 break;
6991 case L5_SPIN:
6992 spinningInertia += 5;
6993 spin_speed += dword_F64E08 >> spinningInertia;
6994 break;
6995 case R7_SPIN:
6996 spinningInertia += 7;
6997 spin_speed -= dword_F64E08 >> spinningInertia;
6998 break;
6999 case L7_SPIN:
7000 spinningInertia += 7;
7001 spin_speed += dword_F64E08 >> spinningInertia;
7002 break;
7003 case RL_SPIN:
7004 // Right Left Curve Track Piece
7005 if (track_progress < 48)
7006 {
7007 // R8_SPIN
7008 spinningInertia += 8;
7009 spin_speed -= dword_F64E08 >> spinningInertia;
7010 break;
7011 }
7012 [[fallthrough]];
7013 case L9_SPIN:
7014 spinningInertia += 9;
7015 spin_speed += dword_F64E08 >> spinningInertia;
7016 break;
7017 case L8_SPIN:
7018 spinningInertia += 8;
7019 spin_speed += dword_F64E08 >> spinningInertia;
7020 break;
7021 case SP_SPIN:
7022 // On rapids spin after fully on them
7023 if (track_progress > 22)
7024 {
7025 // L5_SPIN
7026 spinningInertia += 5;
7027 spin_speed += dword_F64E08 >> spinningInertia;
7028 }
7029 break;
7030 case LR_SPIN:
7031 // Left Right Curve Track Piece
7032 if (track_progress < 48)
7033 {
7034 // L8_SPIN
7035 spinningInertia += 8;
7036 spin_speed += dword_F64E08 >> spinningInertia;
7037 break;
7038 }
7039 [[fallthrough]];
7040 case R9_SPIN:
7041 spinningInertia += 9;
7042 spin_speed -= dword_F64E08 >> spinningInertia;
7043 break;
7044 case R8_SPIN:
7045 spinningInertia += 8;
7046 spin_speed -= dword_F64E08 >> spinningInertia;
7047 break;
7048 }
7049
7050 spinSpeed = std::clamp(spin_speed, VEHICLE_MIN_SPIN_SPEED, VEHICLE_MAX_SPIN_SPEED);
7051 spin_speed = spinSpeed;
7052 spin_sprite += spinSpeed >> 8;
7053 // Note this actually increases the spin speed if going right!
7054 spin_speed -= spinSpeed >> vehicleEntry->spinning_friction;
7055 Invalidate();
7056 }
7057
UpdateAnimationAnimalFlying()7058 void Vehicle::UpdateAnimationAnimalFlying()
7059 {
7060 if (animationState > 0)
7061 {
7062 animationState--;
7063 return;
7064 }
7065
7066 if (animation_frame == 0)
7067 {
7068 auto trackType = GetTrackType();
7069 TileElement* trackElement = map_get_track_element_at_of_type_seq(TrackLocation, trackType, 0);
7070 if (trackElement != nullptr && trackElement->AsTrack()->HasChain())
7071 {
7072 // start flapping, bird
7073 animation_frame = 1;
7074 animationState = 5;
7075 Invalidate();
7076 }
7077 }
7078 else
7079 {
7080 // continue flapping until reaching frame 0
7081 animation_frame = (animation_frame + 1) % 4;
7082 Invalidate();
7083 }
7084 // number of frames to skip before updating again
7085 constexpr std::array frameWaitTimes = { 5, 3, 5, 3 };
7086 animationState = frameWaitTimes[animation_frame];
7087 }
7088
7089 /**
7090 *
7091 * rct2: 0x006D63D4
7092 */
UpdateAdditionalAnimation()7093 void Vehicle::UpdateAdditionalAnimation()
7094 {
7095 uint8_t targetFrame{};
7096 uint8_t curFrame{};
7097 uint32_t eax{};
7098
7099 auto vehicleEntry = Entry();
7100 if (vehicleEntry == nullptr)
7101 {
7102 return;
7103 }
7104 switch (vehicleEntry->animation)
7105 {
7106 case VEHICLE_ENTRY_ANIMATION_MINITURE_RAILWAY_LOCOMOTIVE: // loc_6D652B
7107 animationState += _vehicleVelocityF64E08;
7108 targetFrame = (animationState >> 20) & 3;
7109 if (animation_frame != targetFrame)
7110 {
7111 curFrame = animation_frame;
7112 animation_frame = targetFrame;
7113 targetFrame &= 0x02;
7114 curFrame &= 0x02;
7115 if (targetFrame != curFrame)
7116 {
7117 auto curRide = GetRide();
7118 if (curRide != nullptr)
7119 {
7120 if (!ride_has_station_shelter(curRide)
7121 || (status != Vehicle::Status::MovingToEndOfStation && status != Vehicle::Status::Arriving))
7122 {
7123 int32_t typeIndex = [&] {
7124 switch (Pitch)
7125 {
7126 case 2:
7127 // uphill
7128 return 1;
7129 case 6:
7130 // downhill
7131 return 2;
7132 default:
7133 return 0;
7134 }
7135 }();
7136 int32_t directionIndex = sprite_direction >> 1;
7137 auto offset = SteamParticleOffsets[typeIndex][directionIndex];
7138 SteamParticle::Create({ x + offset.x, y + offset.y, z + offset.z });
7139 }
7140 }
7141 }
7142 Invalidate();
7143 }
7144 break;
7145 case VEHICLE_ENTRY_ANIMATION_SWAN: // loc_6D6424
7146 animationState += _vehicleVelocityF64E08;
7147 targetFrame = (animationState >> 18) & 2;
7148 if (animation_frame != targetFrame)
7149 {
7150 animation_frame = targetFrame;
7151 Invalidate();
7152 }
7153 break;
7154 case VEHICLE_ENTRY_ANIMATION_CANOES: // loc_6D6482
7155 animationState += _vehicleVelocityF64E08;
7156 eax = ((animationState >> 13) & 0xFF) * 6;
7157 targetFrame = (eax >> 8) & 0xFF;
7158 if (animation_frame != targetFrame)
7159 {
7160 animation_frame = targetFrame;
7161 Invalidate();
7162 }
7163 break;
7164 case VEHICLE_ENTRY_ANIMATION_ROW_BOATS: // loc_6D64F7
7165 animationState += _vehicleVelocityF64E08;
7166 eax = ((animationState >> 13) & 0xFF) * 7;
7167 targetFrame = (eax >> 8) & 0xFF;
7168 if (animation_frame != targetFrame)
7169 {
7170 animation_frame = targetFrame;
7171 Invalidate();
7172 }
7173 break;
7174 case VEHICLE_ENTRY_ANIMATION_WATER_TRICYCLES: // loc_6D6453
7175 animationState += _vehicleVelocityF64E08;
7176 targetFrame = (animationState >> 19) & 1;
7177 if (animation_frame != targetFrame)
7178 {
7179 animation_frame = targetFrame;
7180 Invalidate();
7181 }
7182 break;
7183 case VEHICLE_ENTRY_ANIMATION_OBSERVATION_TOWER: // loc_6D65C3
7184 if (animationState <= 0xCCCC)
7185 {
7186 animationState += 0x3333;
7187 }
7188 else
7189 {
7190 animationState = 0;
7191 animation_frame += 1;
7192 animation_frame &= 7;
7193 Invalidate();
7194 }
7195 break;
7196 case VEHICLE_ENTRY_ANIMATION_HELICARS: // loc_6D63F5
7197 animationState += _vehicleVelocityF64E08;
7198 targetFrame = (animationState >> 18) & 3;
7199 if (animation_frame != targetFrame)
7200 {
7201 animation_frame = targetFrame;
7202 Invalidate();
7203 }
7204 break;
7205 case VEHICLE_ENTRY_ANIMATION_MONORAIL_CYCLES: // loc_6D64B6
7206 if (num_peeps != 0)
7207 {
7208 animationState += _vehicleVelocityF64E08;
7209 eax = ((animationState >> 13) & 0xFF) << 2;
7210 targetFrame = (eax >> 8) & 0xFF;
7211 if (animation_frame != targetFrame)
7212 {
7213 animation_frame = targetFrame;
7214 Invalidate();
7215 }
7216 }
7217 break;
7218 case VEHICLE_ENTRY_ANIMATION_MULTI_DIM_COASTER: // loc_6D65E1
7219 if (seat_rotation != target_seat_rotation)
7220 {
7221 if (animationState <= 0xCCCC)
7222 {
7223 animationState += 0x3333;
7224 }
7225 else
7226 {
7227 animationState = 0;
7228
7229 if (seat_rotation >= target_seat_rotation)
7230 seat_rotation--;
7231
7232 else
7233 seat_rotation++;
7234
7235 animation_frame = (seat_rotation - 4) & 7;
7236 Invalidate();
7237 }
7238 }
7239 break;
7240 case VEHICLE_ENTRY_ANIMATION_ANIMAL_FLYING:
7241 UpdateAnimationAnimalFlying();
7242 // makes animation play faster with vehicle speed
7243 targetFrame = abs(_vehicleVelocityF64E08) >> 24;
7244 animationState = std::max(animationState - targetFrame, 0u);
7245 break;
7246 }
7247 }
7248
7249 /**
7250 *
7251 * rct2: 0x006DEDB1
7252 */
play_scenery_door_open_sound(const CoordsXYZ & loc,WallElement * tileElement)7253 static void play_scenery_door_open_sound(const CoordsXYZ& loc, WallElement* tileElement)
7254 {
7255 auto* wallEntry = tileElement->GetEntry();
7256 int32_t doorSoundType = wall_entry_get_door_sound(wallEntry);
7257 if (doorSoundType != 0)
7258 {
7259 auto soundId = DoorOpenSoundIds[doorSoundType - 1];
7260 if (soundId != OpenRCT2::Audio::SoundId::Null)
7261 {
7262 OpenRCT2::Audio::Play3D(soundId, loc);
7263 }
7264 }
7265 }
7266
7267 /**
7268 *
7269 * rct2: 0x006DED7A
7270 */
play_scenery_door_close_sound(const CoordsXYZ & loc,WallElement * tileElement)7271 static void play_scenery_door_close_sound(const CoordsXYZ& loc, WallElement* tileElement)
7272 {
7273 auto* wallEntry = tileElement->GetEntry();
7274 int32_t doorSoundType = wall_entry_get_door_sound(wallEntry);
7275 if (doorSoundType != 0)
7276 {
7277 auto soundId = DoorCloseSoundIds[doorSoundType - 1];
7278 if (soundId != OpenRCT2::Audio::SoundId::Null)
7279 {
7280 Play3D(soundId, loc);
7281 }
7282 }
7283 }
7284
7285 template<bool isBackwards>
AnimateSceneryDoor(const CoordsXYZD & doorLocation,const CoordsXYZ & trackLocation,bool isLastVehicle)7286 static void AnimateSceneryDoor(const CoordsXYZD& doorLocation, const CoordsXYZ& trackLocation, bool isLastVehicle)
7287 {
7288 auto door = map_get_wall_element_at(doorLocation);
7289 if (door == nullptr)
7290 {
7291 return;
7292 }
7293
7294 if (!isLastVehicle && (door->GetAnimationFrame() == 0))
7295 {
7296 door->SetAnimationIsBackwards(isBackwards);
7297 door->SetAnimationFrame(1);
7298 map_animation_create(MAP_ANIMATION_TYPE_WALL_DOOR, doorLocation);
7299 play_scenery_door_open_sound(trackLocation, door);
7300 }
7301
7302 if (isLastVehicle)
7303 {
7304 door->SetAnimationIsBackwards(isBackwards);
7305 door->SetAnimationFrame(6);
7306 play_scenery_door_close_sound(trackLocation, door);
7307 }
7308 }
7309
7310 /**
7311 *
7312 * rct2: 0x006DEE93
7313 */
UpdateSceneryDoor() const7314 void Vehicle::UpdateSceneryDoor() const
7315 {
7316 auto trackType = GetTrackType();
7317 const auto& ted = GetTrackElementDescriptor(trackType);
7318 const rct_preview_track* trackBlock = ted.Block;
7319 while ((trackBlock + 1)->index != 255)
7320 {
7321 trackBlock++;
7322 }
7323 const rct_track_coordinates* trackCoordinates = &ted.Coordinates;
7324 auto wallCoords = CoordsXYZ{ x, y, TrackLocation.z - trackBlock->z + trackCoordinates->z_end }.ToTileStart();
7325 int32_t direction = (GetTrackDirection() + trackCoordinates->rotation_end) & 3;
7326
7327 AnimateSceneryDoor<false>(
7328 { wallCoords, static_cast<Direction>(direction) }, TrackLocation, next_vehicle_on_train == SPRITE_INDEX_NULL);
7329 }
7330
AnimateLandscapeDoor(TrackElement * trackElement,bool isLastVehicle)7331 template<bool isBackwards> static void AnimateLandscapeDoor(TrackElement* trackElement, bool isLastVehicle)
7332 {
7333 auto doorState = isBackwards ? trackElement->GetDoorAState() : trackElement->GetDoorBState();
7334 if (!isLastVehicle && doorState == LANDSCAPE_DOOR_CLOSED)
7335 {
7336 if (isBackwards)
7337 trackElement->SetDoorAState(LANDSCAPE_DOOR_OPEN);
7338 else
7339 trackElement->SetDoorBState(LANDSCAPE_DOOR_OPEN);
7340 // TODO: play door open sound
7341 }
7342
7343 if (isLastVehicle)
7344 {
7345 if (isBackwards)
7346 trackElement->SetDoorAState(LANDSCAPE_DOOR_CLOSED);
7347 else
7348 trackElement->SetDoorBState(LANDSCAPE_DOOR_CLOSED);
7349 // TODO: play door close sound
7350 }
7351 }
7352
UpdateLandscapeDoor() const7353 void Vehicle::UpdateLandscapeDoor() const
7354 {
7355 const auto* currentRide = GetRide();
7356 if (currentRide == nullptr || !currentRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LANDSCAPE_DOORS))
7357 {
7358 return;
7359 }
7360
7361 auto coords = CoordsXYZ{ x, y, TrackLocation.z }.ToTileStart();
7362 auto* tileElement = map_get_track_element_at_from_ride(coords, ride);
7363 if (tileElement != nullptr && tileElement->GetType() == static_cast<uint8_t>(TileElementType::Track))
7364 {
7365 AnimateLandscapeDoor<false>(tileElement->AsTrack(), next_vehicle_on_train == SPRITE_INDEX_NULL);
7366 }
7367 }
7368
7369 /**
7370 *
7371 * rct2: 0x006DB38B
7372 */
PitchAndRollStart(bool useInvertedSprites,TileElement * tileElement)7373 static PitchAndRoll PitchAndRollStart(bool useInvertedSprites, TileElement* tileElement)
7374 {
7375 auto trackType = tileElement->AsTrack()->GetTrackType();
7376 const auto& ted = GetTrackElementDescriptor(trackType);
7377 return PitchAndRoll{ ted.Definition.vangle_start, track_get_actual_bank_3(useInvertedSprites, tileElement) };
7378 }
7379
UpdateGoKartAttemptSwitchLanes()7380 void Vehicle::UpdateGoKartAttemptSwitchLanes()
7381 {
7382 uint16_t probability = 0x8000;
7383 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_6))
7384 {
7385 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_6);
7386 }
7387 else
7388 {
7389 probability = 0x0A3D;
7390 }
7391 if ((scenario_rand() & 0xFFFF) <= probability)
7392 {
7393 // This changes "riding left" to "moving to right lane" and "riding right" to "moving to left lane".
7394 TrackSubposition = VehicleTrackSubposition{ static_cast<uint8_t>(static_cast<uint8_t>(TrackSubposition) + 2U) };
7395 }
7396 }
7397
7398 /**
7399 *
7400 * rct2: 0x006DB545
7401 */
trigger_on_ride_photo(const CoordsXYZ & loc,TileElement * tileElement)7402 static void trigger_on_ride_photo(const CoordsXYZ& loc, TileElement* tileElement)
7403 {
7404 tileElement->AsTrack()->SetPhotoTimeout();
7405
7406 map_animation_create(MAP_ANIMATION_TYPE_TRACK_ONRIDEPHOTO, { loc, tileElement->GetBaseZ() });
7407 }
7408
7409 /**
7410 *
7411 * rct2: 0x006DEDE8
7412 */
UpdateSceneryDoorBackwards() const7413 void Vehicle::UpdateSceneryDoorBackwards() const
7414 {
7415 auto trackType = GetTrackType();
7416 const auto& ted = GetTrackElementDescriptor(trackType);
7417 const rct_preview_track* trackBlock = ted.Block;
7418 const rct_track_coordinates* trackCoordinates = &ted.Coordinates;
7419 auto wallCoords = CoordsXYZ{ TrackLocation, TrackLocation.z - trackBlock->z + trackCoordinates->z_begin };
7420 int32_t direction = (GetTrackDirection() + trackCoordinates->rotation_begin) & 3;
7421 direction = direction_reverse(direction);
7422
7423 AnimateSceneryDoor<true>(
7424 { wallCoords, static_cast<Direction>(direction) }, TrackLocation, next_vehicle_on_train == SPRITE_INDEX_NULL);
7425 }
7426
UpdateLandscapeDoorBackwards() const7427 void Vehicle::UpdateLandscapeDoorBackwards() const
7428 {
7429 const auto* currentRide = GetRide();
7430 if (currentRide == nullptr || !currentRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LANDSCAPE_DOORS))
7431 {
7432 return;
7433 }
7434
7435 auto coords = CoordsXYZ{ TrackLocation, TrackLocation.z };
7436 auto* tileElement = map_get_track_element_at_from_ride(coords, ride);
7437 if (tileElement != nullptr && tileElement->GetType() == static_cast<uint8_t>(TileElementType::Track))
7438 {
7439 AnimateLandscapeDoor<true>(tileElement->AsTrack(), next_vehicle_on_train == SPRITE_INDEX_NULL);
7440 }
7441 }
7442
vehicle_update_play_water_splash_sound()7443 static void vehicle_update_play_water_splash_sound()
7444 {
7445 if (_vehicleVelocityF64E08 <= BLOCK_BRAKE_BASE_SPEED)
7446 {
7447 return;
7448 }
7449
7450 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::WaterSplash, { unk_F64E20.x, unk_F64E20.y, unk_F64E20.z });
7451 }
7452
7453 /**
7454 *
7455 * rct2: 0x006DB59E
7456 */
UpdateHandleWaterSplash() const7457 void Vehicle::UpdateHandleWaterSplash() const
7458 {
7459 rct_ride_entry* rideEntry = GetRideEntry();
7460 auto trackType = GetTrackType();
7461
7462 if (!(rideEntry->flags & RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND))
7463 {
7464 if (rideEntry->flags & RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND_SLIDE)
7465 {
7466 if (IsHead())
7467 {
7468 if (track_element_is_covered(trackType))
7469 {
7470 Vehicle* nextVehicle = GetEntity<Vehicle>(next_vehicle_on_ride);
7471 if (nextVehicle == nullptr)
7472 return;
7473
7474 Vehicle* nextNextVehicle = GetEntity<Vehicle>(nextVehicle->next_vehicle_on_ride);
7475 if (nextNextVehicle == nullptr)
7476 return;
7477 if (!track_element_is_covered(nextNextVehicle->GetTrackType()))
7478 {
7479 if (track_progress == 4)
7480 {
7481 vehicle_update_play_water_splash_sound();
7482 }
7483 }
7484 }
7485 }
7486 }
7487 }
7488 else
7489 {
7490 if (trackType == TrackElemType::Down25ToFlat)
7491 {
7492 if (track_progress == 12)
7493 {
7494 vehicle_update_play_water_splash_sound();
7495 }
7496 }
7497 }
7498 if (IsHead())
7499 {
7500 if (trackType == TrackElemType::Watersplash)
7501 {
7502 if (track_progress == 48)
7503 {
7504 vehicle_update_play_water_splash_sound();
7505 }
7506 }
7507 }
7508 }
7509
7510 /**
7511 *
7512 * rct2: 0x006DB807
7513 */
UpdateReverserCarBogies()7514 void Vehicle::UpdateReverserCarBogies()
7515 {
7516 const auto moveInfo = GetMoveInfo();
7517 MoveTo({ TrackLocation.x + moveInfo->x, TrackLocation.y + moveInfo->y, z });
7518 }
7519
7520 /**
7521 * Collision Detection
7522 * rct2: 0x006DD078
7523 * @param vehicle (esi)
7524 * @param x (ax)
7525 * @param y (cx)
7526 * @param z (dx)
7527 * @param otherVehicleIndex (bp)
7528 */
UpdateMotionCollisionDetection(const CoordsXYZ & loc,uint16_t * otherVehicleIndex)7529 bool Vehicle::UpdateMotionCollisionDetection(const CoordsXYZ& loc, uint16_t* otherVehicleIndex)
7530 {
7531 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_COLLISION_DISABLED))
7532 return false;
7533
7534 auto vehicleEntry = Entry();
7535 if (vehicleEntry == nullptr)
7536 {
7537 return false;
7538 }
7539
7540 if (!(vehicleEntry->flags & VEHICLE_ENTRY_FLAG_BOAT_HIRE_COLLISION_DETECTION))
7541 {
7542 var_C4 = 0;
7543
7544 // If hacking boat hire rides you can end up here
7545 if (otherVehicleIndex == nullptr)
7546 return false;
7547
7548 Vehicle* collideVehicle = GetEntity<Vehicle>(*otherVehicleIndex);
7549 if (collideVehicle == nullptr)
7550 return false;
7551
7552 if (this == collideVehicle)
7553 return false;
7554
7555 int32_t x_diff = abs(loc.x - collideVehicle->x);
7556 if (x_diff > 0x7FFF)
7557 return false;
7558
7559 int32_t y_diff = abs(loc.y - collideVehicle->y);
7560 if (y_diff > 0x7FFF)
7561 return false;
7562
7563 int32_t z_diff = abs(loc.z - collideVehicle->z);
7564 if (x_diff + y_diff + z_diff > 0xFFFF)
7565 return false;
7566
7567 uint16_t ecx = std::min(var_44 + collideVehicle->var_44, 560);
7568 ecx = ((ecx >> 1) * 30) >> 8;
7569
7570 if (x_diff + y_diff + z_diff >= ecx)
7571 return false;
7572
7573 uint8_t direction = (sprite_direction - collideVehicle->sprite_direction + 7) & 0x1F;
7574 return direction < 0xF;
7575 }
7576
7577 CoordsXY location = loc;
7578
7579 bool mayCollide = false;
7580 Vehicle* collideVehicle = nullptr;
7581 for (auto xy_offset : SurroundingTiles)
7582 {
7583 location += xy_offset;
7584
7585 for (auto vehicle2 : EntityTileList<Vehicle>(location))
7586 {
7587 if (vehicle2 == this)
7588 continue;
7589
7590 int32_t z_diff = abs(vehicle2->z - loc.z);
7591
7592 if (z_diff > 16)
7593 continue;
7594
7595 if (vehicle2->ride_subtype == OBJECT_ENTRY_INDEX_NULL)
7596 continue;
7597
7598 auto collideVehicleEntry = vehicle2->Entry();
7599 if (collideVehicleEntry == nullptr)
7600 continue;
7601
7602 if (!(collideVehicleEntry->flags & VEHICLE_ENTRY_FLAG_BOAT_HIRE_COLLISION_DETECTION))
7603 continue;
7604
7605 uint32_t x_diff = abs(vehicle2->x - loc.x);
7606 if (x_diff > 0x7FFF)
7607 continue;
7608
7609 uint32_t y_diff = abs(vehicle2->y - loc.y);
7610 if (y_diff > 0x7FFF)
7611 continue;
7612
7613 VehicleTrackSubposition cl = std::min(TrackSubposition, vehicle2->TrackSubposition);
7614 VehicleTrackSubposition ch = std::max(TrackSubposition, vehicle2->TrackSubposition);
7615 if (cl != ch)
7616 {
7617 if (cl == VehicleTrackSubposition::GoKartsLeftLane && ch == VehicleTrackSubposition::GoKartsRightLane)
7618 continue;
7619 }
7620
7621 uint32_t ecx = var_44 + vehicle2->var_44;
7622 ecx = ((ecx >> 1) * 30) >> 8;
7623
7624 if (x_diff + y_diff >= ecx)
7625 continue;
7626
7627 if (!(collideVehicleEntry->flags & VEHICLE_ENTRY_FLAG_GO_KART))
7628 {
7629 collideVehicle = vehicle2;
7630 mayCollide = true;
7631 break;
7632 }
7633
7634 uint8_t direction = (sprite_direction - vehicle2->sprite_direction - 6) & 0x1F;
7635
7636 if (direction < 0x14)
7637 continue;
7638
7639 uint32_t offsetSpriteDirection = (sprite_direction + 4) & 31;
7640 uint32_t offsetDirection = offsetSpriteDirection >> 3;
7641 uint32_t next_x_diff = abs(loc.x + AvoidCollisionMoveOffset[offsetDirection].x - vehicle2->x);
7642 uint32_t next_y_diff = abs(loc.y + AvoidCollisionMoveOffset[offsetDirection].y - vehicle2->y);
7643
7644 if (next_x_diff + next_y_diff < x_diff + y_diff)
7645 {
7646 collideVehicle = vehicle2;
7647 mayCollide = true;
7648 break;
7649 }
7650 }
7651 if (mayCollide)
7652 {
7653 break;
7654 }
7655 }
7656
7657 if (!mayCollide)
7658 {
7659 var_C4 = 0;
7660 return false;
7661 }
7662
7663 var_C4++;
7664 if (var_C4 < 200)
7665 {
7666 SetUpdateFlag(VEHICLE_UPDATE_FLAG_6);
7667 if (otherVehicleIndex != nullptr)
7668 *otherVehicleIndex = collideVehicle->sprite_index;
7669 return true;
7670 }
7671
7672 // TODO Is it possible for collideVehicle to be NULL?
7673
7674 if (status == Vehicle::Status::MovingToEndOfStation)
7675 {
7676 if (sprite_direction == 0)
7677 {
7678 if (x <= collideVehicle->x)
7679 {
7680 return false;
7681 }
7682 }
7683 else if (sprite_direction == 8)
7684 {
7685 if (y >= collideVehicle->y)
7686 {
7687 return false;
7688 }
7689 }
7690 else if (sprite_direction == 16)
7691 {
7692 if (x >= collideVehicle->x)
7693 {
7694 return false;
7695 }
7696 }
7697 else if (sprite_direction == 24)
7698 {
7699 if (y <= collideVehicle->y)
7700 {
7701 return false;
7702 }
7703 }
7704 }
7705
7706 if (collideVehicle->status == Vehicle::Status::TravellingBoat && status != Vehicle::Status::Arriving
7707 && status != Vehicle::Status::Travelling)
7708 {
7709 return false;
7710 }
7711
7712 SetUpdateFlag(VEHICLE_UPDATE_FLAG_6);
7713 if (otherVehicleIndex != nullptr)
7714 *otherVehicleIndex = collideVehicle->sprite_index;
7715 return true;
7716 }
7717
7718 /**
7719 *
7720 * rct2: 0x006DB7D6
7721 */
ReverseReverserCar()7722 void Vehicle::ReverseReverserCar()
7723 {
7724 Vehicle* previousVehicle = GetEntity<Vehicle>(prev_vehicle_on_ride);
7725 Vehicle* nextVehicle = GetEntity<Vehicle>(next_vehicle_on_ride);
7726 if (previousVehicle == nullptr || nextVehicle == nullptr)
7727 {
7728 return;
7729 }
7730
7731 track_progress = 168;
7732 vehicle_type ^= 1;
7733
7734 previousVehicle->track_progress = 86;
7735 nextVehicle->track_progress = 158;
7736
7737 nextVehicle->UpdateReverserCarBogies();
7738 previousVehicle->UpdateReverserCarBogies();
7739 }
7740
7741 /**
7742 *
7743 * rct2: 0x006DBF3E
7744 */
Sub6DBF3E()7745 void Vehicle::Sub6DBF3E()
7746 {
7747 rct_ride_entry_vehicle* vehicleEntry = Entry();
7748
7749 acceleration /= _vehicleUnkF64E10;
7750 if (TrackSubposition == VehicleTrackSubposition::ChairliftGoingBack)
7751 {
7752 return;
7753 }
7754
7755 auto trackType = GetTrackType();
7756 const auto& ted = GetTrackElementDescriptor(trackType);
7757 if (!(ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN))
7758 {
7759 return;
7760 }
7761
7762 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_3;
7763
7764 TileElement* tileElement = nullptr;
7765 if (map_is_location_valid(TrackLocation))
7766 {
7767 tileElement = map_get_track_element_at_of_type_seq(TrackLocation, trackType, 0);
7768 }
7769
7770 if (tileElement == nullptr)
7771 {
7772 return;
7773 }
7774
7775 if (_vehicleStationIndex == STATION_INDEX_NULL)
7776 {
7777 _vehicleStationIndex = tileElement->AsTrack()->GetStationIndex();
7778 }
7779
7780 if (trackType == TrackElemType::TowerBase && this == gCurrentVehicle)
7781 {
7782 if (track_progress > 3 && !HasUpdateFlag(VEHICLE_UPDATE_FLAG_REVERSING_SHUTTLE))
7783 {
7784 CoordsXYE output;
7785 int32_t outputZ, outputDirection;
7786
7787 CoordsXYE input = { TrackLocation, tileElement };
7788 if (!track_block_get_next(&input, &output, &outputZ, &outputDirection))
7789 {
7790 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_12;
7791 }
7792 }
7793
7794 if (track_progress <= 3)
7795 {
7796 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_STATION;
7797 }
7798 }
7799
7800 if (trackType != TrackElemType::EndStation || this != gCurrentVehicle)
7801 {
7802 return;
7803 }
7804
7805 uint16_t ax = track_progress;
7806 if (_vehicleVelocityF64E08 < 0)
7807 {
7808 if (ax <= 22)
7809 {
7810 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_STATION;
7811 }
7812 }
7813 else
7814 {
7815 uint16_t cx = 17;
7816 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_CHAIRLIFT)
7817 {
7818 cx = 6;
7819 }
7820 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_GO_KART)
7821 {
7822 // Determine the stop positions for the karts. If in left lane it's further along the track than the right lane.
7823 // Since it's not possible to overtake when the race has ended, this does not check for overtake states (7 and
7824 // 8).
7825 cx = TrackSubposition == VehicleTrackSubposition::GoKartsRightLane ? 18 : 20;
7826 }
7827
7828 if (ax > cx)
7829 {
7830 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_STATION;
7831 }
7832 }
7833 }
7834
7835 /**
7836 *
7837 * rct2: 0x006DB08C
7838 */
UpdateTrackMotionForwardsGetNewTrack(uint16_t trackType,Ride * curRide,rct_ride_entry * rideEntry)7839 bool Vehicle::UpdateTrackMotionForwardsGetNewTrack(uint16_t trackType, Ride* curRide, rct_ride_entry* rideEntry)
7840 {
7841 CoordsXYZD location = {};
7842
7843 auto pitchAndRollEnd = TrackPitchAndRollEnd(trackType);
7844 TileElement* tileElement = map_get_track_element_at_of_type_seq(TrackLocation, trackType, 0);
7845
7846 if (tileElement == nullptr)
7847 {
7848 return false;
7849 }
7850
7851 if (trackType == TrackElemType::CableLiftHill && this == gCurrentVehicle)
7852 {
7853 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_11;
7854 }
7855
7856 if (tileElement->AsTrack()->IsBlockStart())
7857 {
7858 if (next_vehicle_on_train == SPRITE_INDEX_NULL)
7859 {
7860 tileElement->AsTrack()->SetBlockBrakeClosed(true);
7861 if (trackType == TrackElemType::BlockBrakes || trackType == TrackElemType::EndStation)
7862 {
7863 if (!(rideEntry->vehicles[0].flags & VEHICLE_ENTRY_FLAG_POWERED))
7864 {
7865 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::BlockBrakeRelease, TrackLocation);
7866 }
7867 }
7868 map_invalidate_element(TrackLocation, tileElement);
7869 block_brakes_open_previous_section(*curRide, TrackLocation, tileElement);
7870 }
7871 }
7872
7873 // Change from original: this used to check if the vehicle allowed doors.
7874 UpdateSceneryDoor();
7875 UpdateLandscapeDoor();
7876
7877 bool isGoingBack = false;
7878 switch (TrackSubposition)
7879 {
7880 case VehicleTrackSubposition::ChairliftGoingBack:
7881 case VehicleTrackSubposition::ChairliftEndBullwheel:
7882 TrackSubposition = VehicleTrackSubposition::ChairliftGoingBack;
7883 isGoingBack = true;
7884 break;
7885 case VehicleTrackSubposition::ChairliftStartBullwheel:
7886 TrackSubposition = VehicleTrackSubposition::ChairliftGoingOut;
7887 break;
7888 case VehicleTrackSubposition::GoKartsMovingToRightLane:
7889 TrackSubposition = VehicleTrackSubposition::GoKartsRightLane;
7890 break;
7891 case VehicleTrackSubposition::GoKartsMovingToLeftLane:
7892 TrackSubposition = VehicleTrackSubposition::GoKartsLeftLane;
7893 break;
7894 default:
7895 break;
7896 }
7897
7898 if (isGoingBack)
7899 {
7900 track_begin_end trackBeginEnd;
7901 if (!track_block_get_previous({ TrackLocation, tileElement }, &trackBeginEnd))
7902 {
7903 return false;
7904 }
7905 location.x = trackBeginEnd.begin_x;
7906 location.y = trackBeginEnd.begin_y;
7907 location.z = trackBeginEnd.begin_z;
7908 location.direction = trackBeginEnd.begin_direction;
7909 tileElement = trackBeginEnd.begin_element;
7910 }
7911 else
7912 {
7913 {
7914 int32_t curZ, direction;
7915 CoordsXYE xyElement = { TrackLocation, tileElement };
7916 if (!track_block_get_next(&xyElement, &xyElement, &curZ, &direction))
7917 {
7918 return false;
7919 }
7920 tileElement = xyElement.element;
7921 location = { xyElement, curZ, static_cast<Direction>(direction) };
7922 }
7923 if (tileElement->AsTrack()->GetTrackType() == TrackElemType::LeftReverser
7924 || tileElement->AsTrack()->GetTrackType() == TrackElemType::RightReverser)
7925 {
7926 if (IsHead() && velocity <= 0x30000)
7927 {
7928 velocity = 0;
7929 }
7930 }
7931
7932 if (PitchAndRollStart(HasUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES), tileElement) != pitchAndRollEnd)
7933 {
7934 return false;
7935 }
7936
7937 // Update VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES flag
7938 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES);
7939 {
7940 int32_t rideType = get_ride(tileElement->AsTrack()->GetRideIndex())->type;
7941 if (GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
7942 {
7943 if (tileElement->AsTrack()->IsInverted())
7944 {
7945 SetUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES);
7946 }
7947 }
7948 }
7949 }
7950
7951 TrackLocation = location;
7952
7953 // TODO check if getting the vehicle entry again is necessary
7954 rct_ride_entry_vehicle* vehicleEntry = Entry();
7955 if (vehicleEntry == nullptr)
7956 {
7957 return false;
7958 }
7959 if ((vehicleEntry->flags & VEHICLE_ENTRY_FLAG_GO_KART)
7960 && TrackSubposition < VehicleTrackSubposition::GoKartsMovingToRightLane)
7961 {
7962 trackType = tileElement->AsTrack()->GetTrackType();
7963 if (trackType == TrackElemType::Flat
7964 || ((curRide->lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING) && tileElement->AsTrack()->IsStation()))
7965 {
7966 UpdateGoKartAttemptSwitchLanes();
7967 }
7968 }
7969
7970 if (TrackSubposition >= VehicleTrackSubposition::ChairliftGoingOut
7971 && TrackSubposition <= VehicleTrackSubposition::ChairliftStartBullwheel)
7972 {
7973 TileCoordsXYZ curLocation{ TrackLocation };
7974
7975 if (curLocation == curRide->ChairliftBullwheelLocation[1])
7976 {
7977 TrackSubposition = VehicleTrackSubposition::ChairliftEndBullwheel;
7978 }
7979 else if (curLocation == curRide->ChairliftBullwheelLocation[0])
7980 {
7981 TrackSubposition = VehicleTrackSubposition::ChairliftStartBullwheel;
7982 }
7983 }
7984
7985 // loc_6DB500
7986 // Update VEHICLE_UPDATE_FLAG_ON_LIFT_HILL
7987 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL);
7988 if (tileElement->AsTrack()->HasChain())
7989 {
7990 SetUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL);
7991 }
7992
7993 trackType = tileElement->AsTrack()->GetTrackType();
7994 if (trackType != TrackElemType::Brakes)
7995 {
7996 target_seat_rotation = tileElement->AsTrack()->GetSeatRotation();
7997 }
7998 SetTrackDirection(location.direction);
7999 SetTrackType(trackType);
8000 brake_speed = tileElement->AsTrack()->GetBrakeBoosterSpeed();
8001 if (trackType == TrackElemType::OnRidePhoto)
8002 {
8003 trigger_on_ride_photo(TrackLocation, tileElement);
8004 }
8005 if (trackType == TrackElemType::RotationControlToggle)
8006 {
8007 update_flags ^= VEHICLE_UPDATE_FLAG_ROTATION_OFF_WILD_MOUSE;
8008 }
8009 // Change from original: this used to check if the vehicle allowed doors.
8010 UpdateSceneryDoorBackwards();
8011 UpdateLandscapeDoorBackwards();
8012
8013 return true;
8014 }
8015
8016 /**
8017 *
8018 * rct2: 0x006DAEB9
8019 */
UpdateTrackMotionForwards(rct_ride_entry_vehicle * vehicleEntry,Ride * curRide,rct_ride_entry * rideEntry)8020 bool Vehicle::UpdateTrackMotionForwards(rct_ride_entry_vehicle* vehicleEntry, Ride* curRide, rct_ride_entry* rideEntry)
8021 {
8022 uint16_t otherVehicleIndex = SPRITE_INDEX_NULL;
8023 loc_6DAEB9:
8024 auto trackType = GetTrackType();
8025 if (trackType == TrackElemType::HeartLineTransferUp || trackType == TrackElemType::HeartLineTransferDown)
8026 {
8027 if (track_progress == 80)
8028 {
8029 vehicle_type ^= 1;
8030 vehicleEntry = Entry();
8031 }
8032 if (_vehicleVelocityF64E08 >= 0x40000)
8033 {
8034 acceleration = -_vehicleVelocityF64E08 * 8;
8035 }
8036 else if (_vehicleVelocityF64E08 < 0x20000)
8037 {
8038 acceleration = 0x50000;
8039 }
8040 }
8041 else if (trackType == TrackElemType::Brakes)
8042 {
8043 bool hasBrakesFailure = curRide->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN
8044 && curRide->breakdown_reason_pending == BREAKDOWN_BRAKES_FAILURE;
8045 if (!hasBrakesFailure || curRide->mechanic_status == RIDE_MECHANIC_STATUS_HAS_FIXED_STATION_BRAKES)
8046 {
8047 auto brakeSpeed = brake_speed << 16;
8048 if (brakeSpeed < _vehicleVelocityF64E08)
8049 {
8050 acceleration = -_vehicleVelocityF64E08 * 16;
8051 }
8052 else if (!(gCurrentTicks & 0x0F))
8053 {
8054 if (_vehicleF64E2C == 0)
8055 {
8056 _vehicleF64E2C++;
8057 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::BrakeRelease, { x, y, z });
8058 }
8059 }
8060 }
8061 }
8062 else if (trackType == TrackElemType::Booster)
8063 {
8064 auto boosterSpeed = get_booster_speed(curRide->type, (brake_speed << 16));
8065 if (boosterSpeed > _vehicleVelocityF64E08)
8066 {
8067 acceleration = GetRideTypeDescriptor(curRide->type).OperatingSettings.BoosterAcceleration
8068 << 16; //_vehicleVelocityF64E08 * 1.2;
8069 }
8070 }
8071
8072 if ((trackType == TrackElemType::Flat && curRide->type == RIDE_TYPE_REVERSE_FREEFALL_COASTER)
8073 || (trackType == TrackElemType::PoweredLift))
8074 {
8075 acceleration = GetRideTypeDescriptor(curRide->type).OperatingSettings.PoweredLiftAcceleration << 16;
8076 }
8077 if (trackType == TrackElemType::BrakeForDrop)
8078 {
8079 if (IsHead())
8080 {
8081 if (!HasUpdateFlag(VEHICLE_UPDATE_FLAG_ON_BRAKE_FOR_DROP))
8082 {
8083 if (track_progress >= 8)
8084 {
8085 acceleration = -_vehicleVelocityF64E08 * 16;
8086 if (track_progress >= 24)
8087 {
8088 SetUpdateFlag(VEHICLE_UPDATE_FLAG_ON_BRAKE_FOR_DROP);
8089 vertical_drop_countdown = 90;
8090 }
8091 }
8092 }
8093 }
8094 }
8095 if (trackType == TrackElemType::LogFlumeReverser)
8096 {
8097 if (track_progress != 16 || velocity < 0x40000)
8098 {
8099 if (track_progress == 32)
8100 {
8101 vehicle_type = vehicleEntry->log_flume_reverser_vehicle_type;
8102 vehicleEntry = Entry();
8103 }
8104 }
8105 else
8106 {
8107 track_progress += 17;
8108 }
8109 }
8110
8111 uint16_t newTrackProgress = track_progress + 1;
8112
8113 // Track Total Progress is in the two bytes before the move info list
8114 uint16_t trackTotalProgress = GetTrackProgress();
8115 if (newTrackProgress >= trackTotalProgress)
8116 {
8117 UpdateCrossings();
8118
8119 if (!UpdateTrackMotionForwardsGetNewTrack(trackType, curRide, rideEntry))
8120 {
8121 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_5;
8122 _vehicleVelocityF64E0C -= remaining_distance + 1;
8123 remaining_distance = -1;
8124 return false;
8125 }
8126 newTrackProgress = 0;
8127 }
8128
8129 track_progress = newTrackProgress;
8130 UpdateHandleWaterSplash();
8131
8132 // loc_6DB706
8133 const auto moveInfo = GetMoveInfo();
8134 trackType = GetTrackType();
8135 uint8_t moveInfovehicleSpriteType;
8136 {
8137 auto loc = TrackLocation
8138 + CoordsXYZ{ moveInfo->x, moveInfo->y, moveInfo->z + GetRideTypeDescriptor(curRide->type).Heights.VehicleZOffset };
8139
8140 uint8_t remainingDistanceFlags = 0;
8141 if (loc.x != unk_F64E20.x)
8142 {
8143 remainingDistanceFlags |= 1;
8144 }
8145 if (loc.y != unk_F64E20.y)
8146 {
8147 remainingDistanceFlags |= 2;
8148 }
8149 if (loc.z != unk_F64E20.z)
8150 {
8151 remainingDistanceFlags |= 4;
8152 }
8153
8154 if (TrackSubposition == VehicleTrackSubposition::ReverserRCFrontBogie
8155 && (trackType == TrackElemType::LeftReverser || trackType == TrackElemType::RightReverser) && track_progress >= 30
8156 && track_progress <= 66)
8157 {
8158 remainingDistanceFlags |= 8;
8159 }
8160
8161 if (TrackSubposition == VehicleTrackSubposition::ReverserRCRearBogie
8162 && (trackType == TrackElemType::LeftReverser || trackType == TrackElemType::RightReverser) && track_progress == 96)
8163 {
8164 ReverseReverserCar();
8165
8166 const rct_vehicle_info* moveInfo2 = GetMoveInfo();
8167 loc.x = x + moveInfo2->x;
8168 loc.y = y + moveInfo2->y;
8169 }
8170
8171 // loc_6DB8A5
8172 remaining_distance -= dword_9A2930[remainingDistanceFlags];
8173 unk_F64E20 = loc;
8174 sprite_direction = moveInfo->direction;
8175 bank_rotation = moveInfo->bank_rotation;
8176 Pitch = moveInfo->Pitch;
8177
8178 moveInfovehicleSpriteType = moveInfo->Pitch;
8179
8180 if ((vehicleEntry->flags & VEHICLE_ENTRY_FLAG_WOODEN_WILD_MOUSE_SWING) && moveInfo->Pitch != 0)
8181 {
8182 SwingSprite = 0;
8183 SwingPosition = 0;
8184 SwingSpeed = 0;
8185 }
8186
8187 // this == frontVehicle
8188 if (this == _vehicleFrontVehicle)
8189 {
8190 if (_vehicleVelocityF64E08 >= 0)
8191 {
8192 otherVehicleIndex = prev_vehicle_on_ride;
8193 if (UpdateMotionCollisionDetection(loc, &otherVehicleIndex))
8194 {
8195 _vehicleVelocityF64E0C -= remaining_distance + 1;
8196 remaining_distance = -1;
8197
8198 // Might need to be bp rather than this, but hopefully not
8199 auto otherVeh = GetEntity<Vehicle>(otherVehicleIndex);
8200 if (otherVeh == nullptr)
8201 {
8202 // This can never happen as prev_vehicle_on_ride will always be set to a vehicle
8203 log_error("Failed to get next vehicle during update!");
8204 return true;
8205 }
8206 auto head = otherVeh->TrainHead();
8207
8208 auto velocityDelta = abs(velocity - head->velocity);
8209 if (!(rideEntry->flags & RIDE_ENTRY_FLAG_DISABLE_COLLISION_CRASHES))
8210 {
8211 if (velocityDelta > 0xE0000)
8212 {
8213 if (!(vehicleEntry->flags & VEHICLE_ENTRY_FLAG_BOAT_HIRE_COLLISION_DETECTION))
8214 {
8215 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_COLLISION;
8216 }
8217 }
8218 }
8219
8220 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_GO_KART)
8221 {
8222 velocity -= velocity >> 2;
8223 }
8224 else
8225 {
8226 int32_t newHeadVelocity = velocity >> 1;
8227 velocity = head->velocity >> 1;
8228 head->velocity = newHeadVelocity;
8229 }
8230 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_1;
8231 return false;
8232 }
8233 }
8234 }
8235 }
8236
8237 // loc_6DB928
8238 if (remaining_distance < 0x368A)
8239 {
8240 return true;
8241 }
8242
8243 acceleration += dword_9A2970[moveInfovehicleSpriteType];
8244 _vehicleUnkF64E10++;
8245 goto loc_6DAEB9;
8246 }
8247
PitchAndRollEnd(Ride * curRide,bool useInvertedSprites,uint16_t trackType,TileElement * tileElement)8248 static PitchAndRoll PitchAndRollEnd(Ride* curRide, bool useInvertedSprites, uint16_t trackType, TileElement* tileElement)
8249 {
8250 bool isInverted = useInvertedSprites ^ tileElement->AsTrack()->IsInverted();
8251 const auto& ted = GetTrackElementDescriptor(trackType);
8252 return { ted.Definition.vangle_end, track_get_actual_bank_2(curRide->type, isInverted, ted.Definition.bank_end) };
8253 }
8254
8255 /**
8256 *
8257 * rct2: 0x006DBAA6
8258 */
UpdateTrackMotionBackwardsGetNewTrack(uint16_t trackType,Ride * curRide,uint16_t * progress)8259 bool Vehicle::UpdateTrackMotionBackwardsGetNewTrack(uint16_t trackType, Ride* curRide, uint16_t* progress)
8260 {
8261 auto pitchAndRollStart = TrackPitchAndRollStart(trackType);
8262 TileElement* tileElement = map_get_track_element_at_of_type_seq(TrackLocation, trackType, 0);
8263
8264 if (tileElement == nullptr)
8265 return false;
8266
8267 bool nextTileBackwards = true;
8268 int32_t direction = 0;
8269 // loc_6DBB08:;
8270 auto trackPos = CoordsXYZ{ TrackLocation.x, TrackLocation.y, 0 };
8271
8272 switch (TrackSubposition)
8273 {
8274 case VehicleTrackSubposition::ChairliftEndBullwheel:
8275 TrackSubposition = VehicleTrackSubposition::ChairliftGoingOut;
8276 break;
8277 case VehicleTrackSubposition::GoKartsMovingToRightLane:
8278 TrackSubposition = VehicleTrackSubposition::GoKartsLeftLane;
8279 break;
8280 case VehicleTrackSubposition::GoKartsMovingToLeftLane:
8281 TrackSubposition = VehicleTrackSubposition::GoKartsRightLane;
8282 break;
8283 case VehicleTrackSubposition::ChairliftGoingBack:
8284 case VehicleTrackSubposition::ChairliftStartBullwheel:
8285 TrackSubposition = VehicleTrackSubposition::ChairliftGoingBack;
8286 nextTileBackwards = false;
8287 break;
8288 default:
8289 break;
8290 }
8291
8292 if (nextTileBackwards)
8293 {
8294 // loc_6DBB7E:;
8295 track_begin_end trackBeginEnd;
8296 if (!track_block_get_previous({ trackPos, tileElement }, &trackBeginEnd))
8297 {
8298 return false;
8299 }
8300 tileElement = trackBeginEnd.begin_element;
8301
8302 trackType = tileElement->AsTrack()->GetTrackType();
8303 if (trackType == TrackElemType::LeftReverser || trackType == TrackElemType::RightReverser)
8304 {
8305 return false;
8306 }
8307
8308 if (PitchAndRollEnd(curRide, HasUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES), trackType, tileElement)
8309 != pitchAndRollStart)
8310 {
8311 return false;
8312 }
8313
8314 // Update VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES
8315 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES);
8316 if (GetRideTypeDescriptor(curRide->type).HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
8317 {
8318 if (tileElement->AsTrack()->IsInverted())
8319 {
8320 SetUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES);
8321 }
8322 }
8323
8324 trackPos = { trackBeginEnd.begin_x, trackBeginEnd.begin_y, trackBeginEnd.begin_z };
8325 direction = trackBeginEnd.begin_direction;
8326 }
8327 else
8328 {
8329 // loc_6DBB4F:;
8330 CoordsXYE input;
8331 CoordsXYE output;
8332 int32_t outputZ{};
8333
8334 input.x = trackPos.x;
8335 input.y = trackPos.y;
8336 input.element = tileElement;
8337 if (!track_block_get_next(&input, &output, &outputZ, &direction))
8338 {
8339 return false;
8340 }
8341 tileElement = output.element;
8342 trackPos = { output, outputZ };
8343 }
8344
8345 // loc_6DBC3B:
8346 TrackLocation = trackPos;
8347
8348 if (TrackSubposition >= VehicleTrackSubposition::ChairliftGoingOut
8349 && TrackSubposition <= VehicleTrackSubposition::ChairliftStartBullwheel)
8350 {
8351 TileCoordsXYZ curLocation{ TrackLocation };
8352
8353 if (curLocation == curRide->ChairliftBullwheelLocation[1])
8354 {
8355 TrackSubposition = VehicleTrackSubposition::ChairliftEndBullwheel;
8356 }
8357 else if (curLocation == curRide->ChairliftBullwheelLocation[0])
8358 {
8359 TrackSubposition = VehicleTrackSubposition::ChairliftStartBullwheel;
8360 }
8361 }
8362
8363 if (tileElement->AsTrack()->HasChain())
8364 {
8365 if (_vehicleVelocityF64E08 < 0)
8366 {
8367 if (next_vehicle_on_train == SPRITE_INDEX_NULL)
8368 {
8369 trackType = tileElement->AsTrack()->GetTrackType();
8370 const auto& ted = GetTrackElementDescriptor(trackType);
8371 if (!(ted.Flags & TRACK_ELEM_FLAG_DOWN))
8372 {
8373 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_9;
8374 }
8375 }
8376 SetUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL);
8377 }
8378 }
8379 else
8380 {
8381 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL))
8382 {
8383 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL);
8384 if (next_vehicle_on_train == SPRITE_INDEX_NULL)
8385 {
8386 if (_vehicleVelocityF64E08 < 0)
8387 {
8388 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_8;
8389 }
8390 }
8391 }
8392 }
8393
8394 trackType = tileElement->AsTrack()->GetTrackType();
8395 if (trackType != TrackElemType::Brakes)
8396 {
8397 target_seat_rotation = tileElement->AsTrack()->GetSeatRotation();
8398 }
8399 direction &= 3;
8400 SetTrackType(trackType);
8401 SetTrackDirection(direction);
8402 brake_speed = tileElement->AsTrack()->GetBrakeBoosterSpeed();
8403
8404 // There are two bytes before the move info list
8405 uint16_t trackTotalProgress = GetTrackProgress();
8406 *progress = trackTotalProgress - 1;
8407 return true;
8408 }
8409
8410 /**
8411 *
8412 * rct2: 0x006DBA33
8413 */
UpdateTrackMotionBackwards(rct_ride_entry_vehicle * vehicleEntry,Ride * curRide,rct_ride_entry * rideEntry)8414 bool Vehicle::UpdateTrackMotionBackwards(rct_ride_entry_vehicle* vehicleEntry, Ride* curRide, rct_ride_entry* rideEntry)
8415 {
8416 uint16_t otherVehicleIndex = SPRITE_INDEX_NULL;
8417
8418 while (true)
8419 {
8420 auto trackType = GetTrackType();
8421 if (trackType == TrackElemType::Flat && curRide->type == RIDE_TYPE_REVERSE_FREEFALL_COASTER)
8422 {
8423 int32_t unkVelocity = _vehicleVelocityF64E08;
8424 if (unkVelocity < -524288)
8425 {
8426 unkVelocity = abs(unkVelocity);
8427 acceleration = unkVelocity * 2;
8428 }
8429 }
8430
8431 if (trackType == TrackElemType::Brakes)
8432 {
8433 if (-(brake_speed << 16) > _vehicleVelocityF64E08)
8434 {
8435 acceleration = _vehicleVelocityF64E08 * -16;
8436 }
8437 }
8438
8439 if (trackType == TrackElemType::Booster)
8440 {
8441 auto boosterSpeed = get_booster_speed(curRide->type, (brake_speed << 16));
8442 if (boosterSpeed < _vehicleVelocityF64E08)
8443 {
8444 acceleration = GetRideTypeDescriptor(curRide->type).OperatingSettings.BoosterAcceleration << 16;
8445 }
8446 }
8447
8448 uint16_t newTrackProgress = track_progress - 1;
8449 if (newTrackProgress == 0xFFFF)
8450 {
8451 UpdateCrossings();
8452
8453 if (!UpdateTrackMotionBackwardsGetNewTrack(trackType, curRide, &newTrackProgress))
8454 {
8455 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_5;
8456 _vehicleVelocityF64E0C -= remaining_distance - 0x368A;
8457 remaining_distance = 0x368A;
8458 return false;
8459 }
8460 }
8461
8462 // loc_6DBD42
8463 track_progress = newTrackProgress;
8464 uint8_t moveInfoVehicleSpriteType;
8465 {
8466 const rct_vehicle_info* moveInfo = GetMoveInfo();
8467 auto loc = TrackLocation
8468 + CoordsXYZ{ moveInfo->x, moveInfo->y,
8469 moveInfo->z + GetRideTypeDescriptor(curRide->type).Heights.VehicleZOffset };
8470
8471 uint8_t remainingDistanceFlags = 0;
8472 if (loc.x != unk_F64E20.x)
8473 {
8474 remainingDistanceFlags |= 1;
8475 }
8476 if (loc.y != unk_F64E20.y)
8477 {
8478 remainingDistanceFlags |= 2;
8479 }
8480 if (loc.z != unk_F64E20.z)
8481 {
8482 remainingDistanceFlags |= 4;
8483 }
8484 remaining_distance += dword_9A2930[remainingDistanceFlags];
8485
8486 unk_F64E20 = loc;
8487 sprite_direction = moveInfo->direction;
8488 bank_rotation = moveInfo->bank_rotation;
8489 Pitch = moveInfo->Pitch;
8490 moveInfoVehicleSpriteType = moveInfo->Pitch;
8491
8492 if ((vehicleEntry->flags & VEHICLE_ENTRY_FLAG_WOODEN_WILD_MOUSE_SWING) && Pitch != 0)
8493 {
8494 SwingSprite = 0;
8495 SwingPosition = 0;
8496 SwingSpeed = 0;
8497 }
8498
8499 if (this == _vehicleFrontVehicle)
8500 {
8501 if (_vehicleVelocityF64E08 < 0)
8502 {
8503 otherVehicleIndex = next_vehicle_on_ride;
8504 if (UpdateMotionCollisionDetection(loc, &otherVehicleIndex))
8505 {
8506 _vehicleVelocityF64E0C -= remaining_distance - 0x368A;
8507 remaining_distance = 0x368A;
8508
8509 Vehicle* v3 = GetEntity<Vehicle>(otherVehicleIndex);
8510 Vehicle* v4 = gCurrentVehicle;
8511 if (v3 == nullptr)
8512 {
8513 return false;
8514 }
8515
8516 if (!(rideEntry->flags & RIDE_ENTRY_FLAG_DISABLE_COLLISION_CRASHES))
8517 {
8518 if (abs(v4->velocity - v3->velocity) > 0xE0000)
8519 {
8520 if (!(vehicleEntry->flags & VEHICLE_ENTRY_FLAG_BOAT_HIRE_COLLISION_DETECTION))
8521 {
8522 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_COLLISION;
8523 }
8524 }
8525 }
8526
8527 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_GO_KART)
8528 {
8529 velocity -= velocity >> 2;
8530 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_2;
8531 }
8532 else
8533 {
8534 int32_t v3Velocity = v3->velocity;
8535 v3->velocity = v4->velocity >> 1;
8536 v4->velocity = v3Velocity >> 1;
8537 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_2;
8538 }
8539
8540 return false;
8541 }
8542 }
8543 }
8544 }
8545
8546 // loc_6DBE3F
8547 if (remaining_distance >= 0)
8548 {
8549 return true;
8550 }
8551 acceleration += dword_9A2970[moveInfoVehicleSpriteType];
8552 _vehicleUnkF64E10++;
8553 }
8554 }
8555
8556 /**
8557 * rct2: 0x006DC3A7
8558 *
8559 *
8560 */
UpdateTrackMotionMiniGolfVehicle(Ride * curRide,rct_ride_entry * rideEntry,rct_ride_entry_vehicle * vehicleEntry)8561 void Vehicle::UpdateTrackMotionMiniGolfVehicle(Ride* curRide, rct_ride_entry* rideEntry, rct_ride_entry_vehicle* vehicleEntry)
8562 {
8563 uint16_t otherVehicleIndex = SPRITE_INDEX_NULL;
8564 TileElement* tileElement = nullptr;
8565 CoordsXYZ trackPos;
8566 int32_t direction{};
8567
8568 _vehicleUnkF64E10 = 1;
8569 acceleration = dword_9A2970[Pitch];
8570 if (!HasUpdateFlag(VEHICLE_UPDATE_FLAG_SINGLE_CAR_POSITION))
8571 {
8572 remaining_distance = _vehicleVelocityF64E0C + remaining_distance;
8573 }
8574 if (remaining_distance >= 0 && remaining_distance < 0x368A)
8575 {
8576 goto loc_6DCE02;
8577 }
8578 sound2_flags &= ~VEHICLE_SOUND2_FLAGS_LIFT_HILL;
8579 unk_F64E20.x = x;
8580 unk_F64E20.y = y;
8581 unk_F64E20.z = z;
8582 Invalidate();
8583 if (remaining_distance < 0)
8584 goto loc_6DCA9A;
8585
8586 loc_6DC462:
8587 if (var_D3 == 0)
8588 {
8589 goto loc_6DC476;
8590 }
8591 var_D3--;
8592 goto loc_6DC985;
8593
8594 loc_6DC476:
8595 if (mini_golf_flags & MiniGolfFlag::Flag2)
8596 {
8597 uint8_t nextFrame = animation_frame + 1;
8598 if (nextFrame < mini_golf_peep_animation_lengths[EnumValue(mini_golf_current_animation)])
8599 {
8600 animation_frame = nextFrame;
8601 goto loc_6DC985;
8602 }
8603 mini_golf_flags &= ~MiniGolfFlag::Flag2;
8604 }
8605
8606 if (mini_golf_flags & MiniGolfFlag::Flag0)
8607 {
8608 auto vehicleIdx = IsHead() ? next_vehicle_on_ride : prev_vehicle_on_ride;
8609 Vehicle* vEDI = GetEntity<Vehicle>(vehicleIdx);
8610 if (vEDI == nullptr)
8611 {
8612 return;
8613 }
8614 if (!(vEDI->mini_golf_flags & MiniGolfFlag::Flag0) || (vEDI->mini_golf_flags & MiniGolfFlag::Flag2))
8615 {
8616 goto loc_6DC985;
8617 }
8618 if (vEDI->var_D3 != 0)
8619 {
8620 goto loc_6DC985;
8621 }
8622 vEDI->mini_golf_flags &= ~MiniGolfFlag::Flag0;
8623 mini_golf_flags &= ~MiniGolfFlag::Flag0;
8624 }
8625
8626 if (mini_golf_flags & MiniGolfFlag::Flag1)
8627 {
8628 auto vehicleIdx = IsHead() ? next_vehicle_on_ride : prev_vehicle_on_ride;
8629 Vehicle* vEDI = GetEntity<Vehicle>(vehicleIdx);
8630 if (vEDI == nullptr)
8631 {
8632 return;
8633 }
8634 if (!(vEDI->mini_golf_flags & MiniGolfFlag::Flag1) || (vEDI->mini_golf_flags & MiniGolfFlag::Flag2))
8635 {
8636 goto loc_6DC985;
8637 }
8638 if (vEDI->var_D3 != 0)
8639 {
8640 goto loc_6DC985;
8641 }
8642 vEDI->mini_golf_flags &= ~MiniGolfFlag::Flag1;
8643 mini_golf_flags &= ~MiniGolfFlag::Flag1;
8644 }
8645
8646 if (mini_golf_flags & MiniGolfFlag::Flag3)
8647 {
8648 Vehicle* vEDI = this;
8649
8650 for (;;)
8651 {
8652 vEDI = GetEntity<Vehicle>(vEDI->prev_vehicle_on_ride);
8653 if (vEDI == this || vEDI == nullptr)
8654 {
8655 break;
8656 }
8657 if (vEDI->IsHead())
8658 continue;
8659 if (!(vEDI->mini_golf_flags & MiniGolfFlag::Flag4))
8660 continue;
8661 if (vEDI->TrackLocation != TrackLocation)
8662 continue;
8663 goto loc_6DC985;
8664 }
8665
8666 mini_golf_flags |= MiniGolfFlag::Flag4;
8667 mini_golf_flags &= ~MiniGolfFlag::Flag3;
8668 }
8669
8670 // There are two bytes before the move info list
8671 {
8672 uint16_t trackTotalProgress = GetTrackProgress();
8673 if (track_progress + 1 < trackTotalProgress)
8674 {
8675 track_progress += 1;
8676 goto loc_6DC743;
8677 }
8678 }
8679
8680 tileElement = map_get_track_element_at_of_type_seq(TrackLocation, GetTrackType(), 0);
8681 {
8682 CoordsXYE output;
8683 int32_t outZ{};
8684 int32_t outDirection{};
8685 CoordsXYE input = { TrackLocation, tileElement };
8686 if (!track_block_get_next(&input, &output, &outZ, &outDirection))
8687 {
8688 goto loc_6DC9BC;
8689 }
8690 tileElement = output.element;
8691 trackPos = { output.x, output.y, outZ };
8692 direction = outDirection;
8693 }
8694
8695 if (PitchAndRollStart(HasUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES), tileElement)
8696 != TrackPitchAndRollEnd(GetTrackType()))
8697 {
8698 goto loc_6DC9BC;
8699 }
8700
8701 {
8702 int32_t rideType = get_ride(tileElement->AsTrack()->GetRideIndex())->type;
8703 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES);
8704 if (GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
8705 {
8706 if (tileElement->AsTrack()->IsInverted())
8707 {
8708 SetUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES);
8709 }
8710 }
8711 }
8712
8713 TrackLocation = trackPos;
8714
8715 if (!IsHead())
8716 {
8717 Vehicle* prevVehicle = GetEntity<Vehicle>(prev_vehicle_on_ride);
8718 if (prevVehicle != nullptr)
8719 {
8720 TrackSubposition = prevVehicle->TrackSubposition;
8721 }
8722 if (TrackSubposition != VehicleTrackSubposition::MiniGolfStart9)
8723 {
8724 TrackSubposition = VehicleTrackSubposition{ static_cast<uint8_t>(static_cast<uint8_t>(TrackSubposition) - 1U) };
8725 }
8726 }
8727
8728 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL);
8729 SetTrackType(tileElement->AsTrack()->GetTrackType());
8730 SetTrackDirection(direction);
8731 var_CF = tileElement->AsTrack()->GetBrakeBoosterSpeed();
8732 track_progress = 0;
8733
8734 loc_6DC743:
8735 if (!IsHead())
8736 {
8737 animation_frame++;
8738 if (animation_frame >= 6)
8739 {
8740 animation_frame = 0;
8741 }
8742 }
8743 const rct_vehicle_info* moveInfo;
8744 for (;;)
8745 {
8746 moveInfo = GetMoveInfo();
8747 if (moveInfo->x != LOCATION_NULL)
8748 {
8749 break;
8750 }
8751 switch (MiniGolfState(moveInfo->y))
8752 {
8753 case MiniGolfState::Unk0: // loc_6DC7B4
8754 if (!IsHead())
8755 {
8756 mini_golf_flags |= MiniGolfFlag::Flag3;
8757 }
8758 else
8759 {
8760 uint16_t rand16 = scenario_rand() & 0xFFFF;
8761 VehicleTrackSubposition nextTrackSubposition = VehicleTrackSubposition::MiniGolfBallPathC14;
8762 if (rand16 <= 0xA000)
8763 {
8764 nextTrackSubposition = VehicleTrackSubposition::MiniGolfBallPathB12;
8765 if (rand16 <= 0x900)
8766 {
8767 nextTrackSubposition = VehicleTrackSubposition::MiniGolfBallPathA10;
8768 }
8769 }
8770 TrackSubposition = nextTrackSubposition;
8771 }
8772 track_progress++;
8773 break;
8774 case MiniGolfState::Unk1: // loc_6DC7ED
8775 log_error("Unused move info...");
8776 assert(false);
8777 var_D3 = static_cast<uint8_t>(moveInfo->z);
8778 track_progress++;
8779 break;
8780 case MiniGolfState::Unk2: // loc_6DC800
8781 mini_golf_flags |= MiniGolfFlag::Flag0;
8782 track_progress++;
8783 break;
8784 case MiniGolfState::Unk3: // loc_6DC810
8785 mini_golf_flags |= MiniGolfFlag::Flag1;
8786 track_progress++;
8787 break;
8788 case MiniGolfState::Unk4: // loc_6DC820
8789 {
8790 auto animation = MiniGolfAnimation(moveInfo->z);
8791 // When the ride is closed occasionally the peep is removed
8792 // but the vehicle is still on the track. This will prevent
8793 // it from crashing in that situation.
8794 auto* curPeep = TryGetEntity<Guest>(peep[0]);
8795 if (curPeep != nullptr)
8796 {
8797 if (animation == MiniGolfAnimation::SwingLeft)
8798 {
8799 if (curPeep->Id & 7)
8800 {
8801 animation = MiniGolfAnimation::Swing;
8802 }
8803 }
8804 if (animation == MiniGolfAnimation::PuttLeft)
8805 {
8806 if (curPeep->Id & 7)
8807 {
8808 animation = MiniGolfAnimation::Putt;
8809 }
8810 }
8811 }
8812 mini_golf_current_animation = animation;
8813 animation_frame = 0;
8814 track_progress++;
8815 break;
8816 }
8817 case MiniGolfState::Unk5: // loc_6DC87A
8818 mini_golf_flags |= MiniGolfFlag::Flag2;
8819 track_progress++;
8820 break;
8821 case MiniGolfState::Unk6: // loc_6DC88A
8822 mini_golf_flags &= ~MiniGolfFlag::Flag4;
8823 mini_golf_flags |= MiniGolfFlag::Flag5;
8824 track_progress++;
8825 break;
8826 default:
8827 log_error("Invalid move info...");
8828 assert(false);
8829 break;
8830 }
8831 }
8832
8833 // loc_6DC8A1
8834 trackPos = { TrackLocation.x + moveInfo->x, TrackLocation.y + moveInfo->y,
8835 TrackLocation.z + moveInfo->z + GetRideTypeDescriptor(curRide->type).Heights.VehicleZOffset };
8836
8837 remaining_distance -= 0x368A;
8838 if (remaining_distance < 0)
8839 {
8840 remaining_distance = 0;
8841 }
8842
8843 unk_F64E20 = trackPos;
8844 sprite_direction = moveInfo->direction;
8845 bank_rotation = moveInfo->bank_rotation;
8846 Pitch = moveInfo->Pitch;
8847
8848 if (rideEntry->vehicles[0].flags & VEHICLE_ENTRY_FLAG_WOODEN_WILD_MOUSE_SWING)
8849 {
8850 if (Pitch != 0)
8851 {
8852 SwingSprite = 0;
8853 SwingPosition = 0;
8854 SwingSpeed = 0;
8855 }
8856 }
8857
8858 if (this == _vehicleFrontVehicle)
8859 {
8860 if (_vehicleVelocityF64E08 >= 0)
8861 {
8862 otherVehicleIndex = prev_vehicle_on_ride;
8863 UpdateMotionCollisionDetection(trackPos, &otherVehicleIndex);
8864 }
8865 }
8866 goto loc_6DC99A;
8867
8868 loc_6DC985:
8869 remaining_distance -= 0x368A;
8870 if (remaining_distance < 0)
8871 {
8872 remaining_distance = 0;
8873 }
8874
8875 loc_6DC99A:
8876 if (remaining_distance < 0x368A)
8877 {
8878 goto loc_6DCDE4;
8879 }
8880 acceleration = dword_9A2970[Pitch];
8881 _vehicleUnkF64E10++;
8882 goto loc_6DC462;
8883
8884 loc_6DC9BC:
8885 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_5;
8886 _vehicleVelocityF64E0C -= remaining_distance + 1;
8887 remaining_distance = -1;
8888 goto loc_6DCD2B;
8889
8890 loc_6DCA9A:
8891 if (track_progress != 0)
8892 {
8893 track_progress -= 1;
8894 goto loc_6DCC2C;
8895 }
8896
8897 tileElement = map_get_track_element_at_of_type_seq(TrackLocation, GetTrackType(), 0);
8898 {
8899 track_begin_end trackBeginEnd;
8900 if (!track_block_get_previous({ TrackLocation, tileElement }, &trackBeginEnd))
8901 {
8902 goto loc_6DC9BC;
8903 }
8904 trackPos = { trackBeginEnd.begin_x, trackBeginEnd.begin_y, trackBeginEnd.begin_z };
8905 direction = trackBeginEnd.begin_direction;
8906 tileElement = trackBeginEnd.begin_element;
8907 }
8908
8909 if (PitchAndRollStart(HasUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES), tileElement)
8910 != TrackPitchAndRollEnd(GetTrackType()))
8911 {
8912 goto loc_6DCD4A;
8913 }
8914
8915 {
8916 int32_t rideType = get_ride(tileElement->AsTrack()->GetRideIndex())->type;
8917 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES);
8918 if (GetRideTypeDescriptor(rideType).HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE))
8919 {
8920 if (tileElement->AsTrack()->IsInverted())
8921 {
8922 SetUpdateFlag(VEHICLE_UPDATE_FLAG_USE_INVERTED_SPRITES);
8923 }
8924 }
8925 }
8926
8927 TrackLocation = trackPos;
8928
8929 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL))
8930 {
8931 ClearUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL);
8932 if (next_vehicle_on_train == SPRITE_INDEX_NULL)
8933 {
8934 if (_vehicleVelocityF64E08 < 0)
8935 {
8936 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_8;
8937 }
8938 }
8939 }
8940
8941 SetTrackType(tileElement->AsTrack()->GetTrackType());
8942 SetTrackDirection(direction);
8943 var_CF = tileElement->AsTrack()->GetSeatRotation() << 1;
8944
8945 // There are two bytes before the move info list
8946 track_progress = GetTrackProgress();
8947
8948 loc_6DCC2C:
8949 moveInfo = GetMoveInfo();
8950 trackPos = { TrackLocation.x + moveInfo->x, TrackLocation.y + moveInfo->y,
8951 TrackLocation.z + moveInfo->z + GetRideTypeDescriptor(curRide->type).Heights.VehicleZOffset };
8952
8953 remaining_distance -= 0x368A;
8954 if (remaining_distance < 0)
8955 {
8956 remaining_distance = 0;
8957 }
8958
8959 unk_F64E20 = trackPos;
8960 sprite_direction = moveInfo->direction;
8961 bank_rotation = moveInfo->bank_rotation;
8962 Pitch = moveInfo->Pitch;
8963
8964 if (rideEntry->vehicles[0].flags & VEHICLE_ENTRY_FLAG_WOODEN_WILD_MOUSE_SWING)
8965 {
8966 if (Pitch != 0)
8967 {
8968 SwingSprite = 0;
8969 SwingPosition = 0;
8970 SwingSpeed = 0;
8971 }
8972 }
8973
8974 if (this == _vehicleFrontVehicle)
8975 {
8976 if (_vehicleVelocityF64E08 >= 0)
8977 {
8978 otherVehicleIndex = var_44;
8979 if (UpdateMotionCollisionDetection(trackPos, &otherVehicleIndex))
8980 {
8981 goto loc_6DCD6B;
8982 }
8983 }
8984 }
8985
8986 loc_6DCD2B:
8987 if (remaining_distance >= 0)
8988 {
8989 goto loc_6DCDE4;
8990 }
8991 acceleration += dword_9A2970[Pitch];
8992 _vehicleUnkF64E10++;
8993 goto loc_6DCA9A;
8994
8995 loc_6DCD4A:
8996 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_5;
8997 _vehicleVelocityF64E0C -= remaining_distance - 0x368A;
8998 remaining_distance = 0x368A;
8999 goto loc_6DC99A;
9000
9001 loc_6DCD6B:
9002 _vehicleVelocityF64E0C -= remaining_distance - 0x368A;
9003 remaining_distance = 0x368A;
9004 {
9005 Vehicle* vEBP = GetEntity<Vehicle>(otherVehicleIndex);
9006 if (vEBP == nullptr)
9007 {
9008 return;
9009 }
9010 Vehicle* vEDI = gCurrentVehicle;
9011 if (abs(vEDI->velocity - vEBP->velocity) > 0xE0000)
9012 {
9013 if (!(vehicleEntry->flags & VEHICLE_ENTRY_FLAG_BOAT_HIRE_COLLISION_DETECTION))
9014 {
9015 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_COLLISION;
9016 }
9017 }
9018 vEDI->velocity = vEBP->velocity >> 1;
9019 vEBP->velocity = vEDI->velocity >> 1;
9020 }
9021 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_2;
9022 goto loc_6DC99A;
9023
9024 loc_6DCDE4:
9025 MoveTo(unk_F64E20);
9026
9027 loc_6DCE02:
9028 acceleration /= _vehicleUnkF64E10;
9029 if (TrackSubposition == VehicleTrackSubposition::ChairliftGoingBack)
9030 {
9031 return;
9032 }
9033 {
9034 auto trackType = GetTrackType();
9035 const auto& ted = GetTrackElementDescriptor(trackType);
9036 if (!(ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN))
9037 {
9038 return;
9039 }
9040 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_3;
9041 if (trackType != TrackElemType::EndStation)
9042 {
9043 return;
9044 }
9045 }
9046 if (this != gCurrentVehicle)
9047 {
9048 return;
9049 }
9050 if (_vehicleVelocityF64E08 < 0)
9051 {
9052 if (track_progress > 11)
9053 {
9054 return;
9055 }
9056 }
9057 if (track_progress <= 8)
9058 {
9059 return;
9060 }
9061
9062 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_AT_STATION;
9063
9064 for (int32_t i = 0; i < MAX_STATIONS; i++)
9065 {
9066 if (TrackLocation != curRide->stations[i].Start)
9067 {
9068 continue;
9069 }
9070 if (TrackLocation.z != curRide->stations[i].GetBaseZ())
9071 {
9072 continue;
9073 }
9074 _vehicleStationIndex = i;
9075 }
9076 }
9077
GetAccelerationDecrease2(const int32_t velocity,const int32_t totalMass)9078 static constexpr int32_t GetAccelerationDecrease2(const int32_t velocity, const int32_t totalMass)
9079 {
9080 int32_t accelerationDecrease2 = velocity >> 8;
9081 accelerationDecrease2 *= accelerationDecrease2;
9082 if (velocity < 0)
9083 {
9084 accelerationDecrease2 = -accelerationDecrease2;
9085 }
9086 accelerationDecrease2 >>= 4;
9087 // OpenRCT2: vehicles from different track types can have 0 mass.
9088 if (totalMass != 0)
9089 {
9090 return accelerationDecrease2 / totalMass;
9091 }
9092
9093 return accelerationDecrease2;
9094 }
9095
UpdateTrackMotionMiniGolfCalculateAcceleration(const rct_ride_entry_vehicle & vehicleEntry)9096 int32_t Vehicle::UpdateTrackMotionMiniGolfCalculateAcceleration(const rct_ride_entry_vehicle& vehicleEntry)
9097 {
9098 int32_t sumAcceleration = 0;
9099 int32_t numVehicles = 0;
9100 uint16_t totalMass = 0;
9101
9102 for (Vehicle* vehicle = this; vehicle != nullptr; vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
9103 {
9104 numVehicles++;
9105 totalMass += vehicle->mass;
9106 sumAcceleration += vehicle->acceleration;
9107 }
9108
9109 int32_t newAcceleration = ((sumAcceleration / numVehicles) * 21) >> 9;
9110 newAcceleration -= velocity >> 12;
9111 newAcceleration -= GetAccelerationDecrease2(velocity, totalMass);
9112
9113 if (!(vehicleEntry.flags & VEHICLE_ENTRY_FLAG_POWERED))
9114 {
9115 return newAcceleration;
9116 }
9117 if (vehicleEntry.flags & VEHICLE_ENTRY_FLAG_POWERED_RIDE_UNRESTRICTED_GRAVITY)
9118 {
9119 if (speed * 0x4000 < velocity)
9120 {
9121 return newAcceleration;
9122 }
9123 }
9124 {
9125 int32_t poweredAcceleration = speed << 14;
9126 int32_t quarterForce = (speed * totalMass) >> 2;
9127 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_REVERSING_SHUTTLE))
9128 {
9129 poweredAcceleration = -poweredAcceleration;
9130 }
9131 poweredAcceleration -= velocity;
9132 poweredAcceleration *= powered_acceleration << 1;
9133 if (quarterForce != 0)
9134 poweredAcceleration /= quarterForce;
9135
9136 if (vehicleEntry.flags & VEHICLE_ENTRY_FLAG_WATER_RIDE)
9137 {
9138 if (poweredAcceleration < 0)
9139 {
9140 poweredAcceleration >>= 4;
9141 }
9142
9143 if (vehicleEntry.flags & VEHICLE_ENTRY_FLAG_SPINNING)
9144 {
9145 spin_speed = std::clamp(spin_speed, VEHICLE_MIN_SPIN_SPEED_WATER_RIDE, VEHICLE_MAX_SPIN_SPEED_WATER_RIDE);
9146 }
9147
9148 if (Pitch != 0)
9149 {
9150 poweredAcceleration = std::max(0, poweredAcceleration);
9151 if (vehicleEntry.flags & VEHICLE_ENTRY_FLAG_SPINNING)
9152 {
9153 if (Pitch == 2)
9154 {
9155 spin_speed = 0;
9156 }
9157 }
9158 newAcceleration += poweredAcceleration;
9159 return newAcceleration;
9160 }
9161 }
9162
9163 if (abs(velocity) > 0x10000)
9164 {
9165 newAcceleration = 0;
9166 }
9167 newAcceleration += poweredAcceleration;
9168 }
9169
9170 return newAcceleration;
9171 }
9172
UpdateTrackMotionMiniGolf(int32_t * outStation)9173 int32_t Vehicle::UpdateTrackMotionMiniGolf(int32_t* outStation)
9174 {
9175 auto curRide = GetRide();
9176 if (curRide == nullptr)
9177 return 0;
9178
9179 rct_ride_entry* rideEntry = GetRideEntry();
9180 rct_ride_entry_vehicle* vehicleEntry = Entry();
9181
9182 gCurrentVehicle = this;
9183 _vehicleMotionTrackFlags = 0;
9184 velocity += acceleration;
9185 _vehicleVelocityF64E08 = velocity;
9186 _vehicleVelocityF64E0C = (velocity >> 10) * 42;
9187 _vehicleFrontVehicle = _vehicleVelocityF64E08 < 0 ? TrainTail() : this;
9188
9189 for (Vehicle* vehicle = _vehicleFrontVehicle; vehicle != nullptr;)
9190 {
9191 vehicle->UpdateTrackMotionMiniGolfVehicle(curRide, rideEntry, vehicleEntry);
9192 if (vehicle->HasUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL))
9193 {
9194 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_ON_LIFT_HILL;
9195 }
9196 if (vehicle->HasUpdateFlag(VEHICLE_UPDATE_FLAG_SINGLE_CAR_POSITION))
9197 {
9198 if (outStation != nullptr)
9199 *outStation = _vehicleStationIndex;
9200 return _vehicleMotionTrackFlags;
9201 }
9202 if (_vehicleVelocityF64E08 >= 0)
9203 {
9204 vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train);
9205 }
9206 else
9207 {
9208 if (vehicle == gCurrentVehicle)
9209 {
9210 break;
9211 }
9212 vehicle = GetEntity<Vehicle>(vehicle->prev_vehicle_on_ride);
9213 }
9214 }
9215
9216 acceleration = UpdateTrackMotionMiniGolfCalculateAcceleration(*vehicleEntry);
9217
9218 if (outStation != nullptr)
9219 *outStation = _vehicleStationIndex;
9220 return _vehicleMotionTrackFlags;
9221 }
9222
9223 /**
9224 *
9225 * rct2: 0x006DC1E4
9226 */
modified_speed(uint16_t trackType,VehicleTrackSubposition trackSubposition,uint8_t speed)9227 static uint8_t modified_speed(uint16_t trackType, VehicleTrackSubposition trackSubposition, uint8_t speed)
9228 {
9229 enum
9230 {
9231 FULL_SPEED,
9232 THREE_QUARTER_SPEED,
9233 HALF_SPEED
9234 };
9235
9236 uint8_t speedModifier = FULL_SPEED;
9237
9238 if (trackType == TrackElemType::LeftQuarterTurn1Tile)
9239 {
9240 speedModifier = (trackSubposition == VehicleTrackSubposition::GoKartsLeftLane) ? HALF_SPEED : THREE_QUARTER_SPEED;
9241 }
9242 else if (trackType == TrackElemType::RightQuarterTurn1Tile)
9243 {
9244 speedModifier = (trackSubposition == VehicleTrackSubposition::GoKartsRightLane) ? HALF_SPEED : THREE_QUARTER_SPEED;
9245 }
9246
9247 switch (speedModifier)
9248 {
9249 case HALF_SPEED:
9250 return speed >> 1;
9251 case THREE_QUARTER_SPEED:
9252 return speed - (speed >> 2);
9253 }
9254 return speed;
9255 }
9256
UpdateTrackMotionPoweredRideAcceleration(rct_ride_entry_vehicle * vehicleEntry,uint32_t totalMass,const int32_t curAcceleration)9257 int32_t Vehicle::UpdateTrackMotionPoweredRideAcceleration(
9258 rct_ride_entry_vehicle* vehicleEntry, uint32_t totalMass, const int32_t curAcceleration)
9259 {
9260 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_POWERED_RIDE_UNRESTRICTED_GRAVITY)
9261 {
9262 if (velocity > (speed * 0x4000))
9263 {
9264 // Same code as none powered rides
9265 if (curAcceleration <= 0 && curAcceleration >= -500 && velocity <= 0x8000)
9266 {
9267 return curAcceleration + 400;
9268 }
9269 return curAcceleration;
9270 }
9271 }
9272 uint8_t modifiedSpeed = modified_speed(GetTrackType(), TrackSubposition, speed);
9273 int32_t poweredAcceleration = modifiedSpeed << 14;
9274 int32_t quarterForce = (modifiedSpeed * totalMass) >> 2;
9275 if (HasUpdateFlag(VEHICLE_UPDATE_FLAG_REVERSING_SHUTTLE))
9276 {
9277 poweredAcceleration = -poweredAcceleration;
9278 }
9279 poweredAcceleration -= velocity;
9280 poweredAcceleration *= powered_acceleration << 1;
9281 if (quarterForce != 0)
9282 {
9283 poweredAcceleration /= quarterForce;
9284 }
9285
9286 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_LIFT)
9287 {
9288 poweredAcceleration *= 4;
9289 }
9290
9291 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_WATER_RIDE)
9292 {
9293 if (poweredAcceleration < 0)
9294 {
9295 poweredAcceleration >>= 4;
9296 }
9297
9298 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_SPINNING)
9299 {
9300 spin_speed = std::clamp(spin_speed, VEHICLE_MIN_SPIN_SPEED_WATER_RIDE, VEHICLE_MAX_SPIN_SPEED_WATER_RIDE);
9301 }
9302
9303 if (Pitch != 0)
9304 {
9305 if (poweredAcceleration < 0)
9306 {
9307 poweredAcceleration = 0;
9308 }
9309
9310 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_SPINNING)
9311 {
9312 // If the vehicle is on the up slope kill the spin speedModifier
9313 if (Pitch == 2)
9314 {
9315 spin_speed = 0;
9316 }
9317 }
9318 return curAcceleration + poweredAcceleration;
9319 }
9320 }
9321
9322 if (std::abs(velocity) <= 0x10000)
9323 {
9324 return poweredAcceleration;
9325 }
9326
9327 return curAcceleration + poweredAcceleration;
9328 }
9329
9330 /**
9331 *
9332 * rct2: 0x006DAB4C
9333 */
UpdateTrackMotion(int32_t * outStation)9334 int32_t Vehicle::UpdateTrackMotion(int32_t* outStation)
9335 {
9336 auto curRide = GetRide();
9337 if (curRide == nullptr)
9338 return 0;
9339
9340 rct_ride_entry* rideEntry = GetRideEntry();
9341 auto vehicleEntry = Entry();
9342
9343 if (vehicleEntry == nullptr)
9344 {
9345 return 0;
9346 }
9347
9348 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_MINI_GOLF)
9349 {
9350 return UpdateTrackMotionMiniGolf(outStation);
9351 }
9352
9353 _vehicleF64E2C = 0;
9354 gCurrentVehicle = this;
9355 _vehicleMotionTrackFlags = 0;
9356 _vehicleStationIndex = STATION_INDEX_NULL;
9357
9358 UpdateTrackMotionUpStopCheck();
9359 CheckAndApplyBlockSectionStopSite();
9360 UpdateVelocity();
9361
9362 Vehicle* vehicle = this;
9363 if (_vehicleVelocityF64E08 < 0)
9364 {
9365 vehicle = vehicle->TrainTail();
9366 }
9367 // This will be the front vehicle even when traveling
9368 // backwards.
9369 _vehicleFrontVehicle = vehicle;
9370
9371 uint16_t spriteId = vehicle->sprite_index;
9372 while (spriteId != SPRITE_INDEX_NULL)
9373 {
9374 Vehicle* car = GetEntity<Vehicle>(spriteId);
9375 if (car == nullptr)
9376 {
9377 break;
9378 }
9379 vehicleEntry = car->Entry();
9380 if (vehicleEntry == nullptr)
9381 {
9382 goto loc_6DBF3E;
9383 }
9384
9385 // Swinging cars
9386 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_SWINGING)
9387 {
9388 car->UpdateSwingingCar();
9389 }
9390 // Spinning cars
9391 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_SPINNING)
9392 {
9393 car->UpdateSpinningCar();
9394 }
9395 // Rider sprites?? animation??
9396 if ((vehicleEntry->flags & VEHICLE_ENTRY_FLAG_VEHICLE_ANIMATION)
9397 || (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_RIDER_ANIMATION))
9398 {
9399 car->UpdateAdditionalAnimation();
9400 }
9401 car->acceleration = dword_9A2970[car->Pitch];
9402 _vehicleUnkF64E10 = 1;
9403
9404 if (!car->HasUpdateFlag(VEHICLE_UPDATE_FLAG_SINGLE_CAR_POSITION))
9405 {
9406 car->remaining_distance += _vehicleVelocityF64E0C;
9407 }
9408
9409 car->sound2_flags &= ~VEHICLE_SOUND2_FLAGS_LIFT_HILL;
9410 unk_F64E20.x = car->x;
9411 unk_F64E20.y = car->y;
9412 unk_F64E20.z = car->z;
9413 car->Invalidate();
9414
9415 while (true)
9416 {
9417 if (car->remaining_distance < 0)
9418 {
9419 // Backward loop
9420 if (car->UpdateTrackMotionBackwards(vehicleEntry, curRide, rideEntry))
9421 {
9422 break;
9423 }
9424
9425 if (car->remaining_distance < 0x368A)
9426 {
9427 break;
9428 }
9429 car->acceleration += dword_9A2970[car->Pitch];
9430 _vehicleUnkF64E10++;
9431 continue;
9432 }
9433 if (car->remaining_distance < 0x368A)
9434 {
9435 // Location found
9436 goto loc_6DBF3E;
9437 }
9438 if (car->UpdateTrackMotionForwards(vehicleEntry, curRide, rideEntry))
9439 {
9440 break;
9441 }
9442
9443 if (car->remaining_distance >= 0)
9444 {
9445 break;
9446 }
9447 car->acceleration = dword_9A2970[car->Pitch];
9448 _vehicleUnkF64E10++;
9449 continue;
9450 }
9451 // loc_6DBF20
9452 car->MoveTo(unk_F64E20);
9453
9454 loc_6DBF3E:
9455 car->Sub6DBF3E();
9456
9457 // loc_6DC0F7
9458 if (car->HasUpdateFlag(VEHICLE_UPDATE_FLAG_ON_LIFT_HILL))
9459 {
9460 _vehicleMotionTrackFlags |= VEHICLE_UPDATE_MOTION_TRACK_FLAG_VEHICLE_ON_LIFT_HILL;
9461 }
9462 if (car->HasUpdateFlag(VEHICLE_UPDATE_FLAG_SINGLE_CAR_POSITION))
9463 {
9464 if (outStation != nullptr)
9465 *outStation = _vehicleStationIndex;
9466 return _vehicleMotionTrackFlags;
9467 }
9468 if (_vehicleVelocityF64E08 >= 0)
9469 {
9470 spriteId = car->next_vehicle_on_train;
9471 }
9472 else
9473 {
9474 if (car == gCurrentVehicle)
9475 {
9476 break;
9477 }
9478 spriteId = car->prev_vehicle_on_ride;
9479 }
9480 }
9481 // loc_6DC144
9482 vehicle = gCurrentVehicle;
9483
9484 vehicleEntry = vehicle->Entry();
9485 // eax
9486 int32_t totalAcceleration = 0;
9487 // ebp
9488 int32_t totalMass = 0;
9489 // ebx
9490 int32_t numVehicles = 0;
9491
9492 for (; vehicle != nullptr; vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
9493 {
9494 numVehicles++;
9495 totalMass += vehicle->mass;
9496 totalAcceleration += vehicle->acceleration;
9497 }
9498
9499 vehicle = gCurrentVehicle;
9500 int32_t newAcceleration = (totalAcceleration / numVehicles) * 21;
9501 if (newAcceleration < 0)
9502 {
9503 newAcceleration += 511;
9504 }
9505 newAcceleration >>= 9;
9506
9507 int32_t curAcceleration = newAcceleration;
9508 curAcceleration -= vehicle->velocity / 4096;
9509 curAcceleration -= GetAccelerationDecrease2(vehicle->velocity, totalMass);
9510
9511 if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_POWERED)
9512 {
9513 curAcceleration = vehicle->UpdateTrackMotionPoweredRideAcceleration(vehicleEntry, totalMass, curAcceleration);
9514 }
9515 else if (curAcceleration <= 0 && curAcceleration >= -500)
9516 {
9517 // Probably moving slowly on a flat track piece, low rolling resistance and drag.
9518
9519 if (vehicle->velocity <= 0x8000 && vehicle->velocity >= 0)
9520 {
9521 // Vehicle is creeping forwards very slowly (less than ~2km/h), boost speed a bit.
9522 curAcceleration += 400;
9523 }
9524 }
9525
9526 if (vehicle->GetTrackType() == TrackElemType::Watersplash)
9527 {
9528 if (vehicle->track_progress >= 48 && vehicle->track_progress <= 128)
9529 {
9530 curAcceleration -= vehicle->velocity >> 6;
9531 }
9532 }
9533
9534 if (rideEntry->flags & RIDE_ENTRY_FLAG_PLAY_SPLASH_SOUND_SLIDE)
9535 {
9536 if (vehicle->IsHead())
9537 {
9538 if (track_element_is_covered(vehicle->GetTrackType()))
9539 {
9540 if (vehicle->velocity > 0x20000)
9541 {
9542 curAcceleration -= vehicle->velocity >> 6;
9543 }
9544 }
9545 }
9546 }
9547
9548 vehicle->acceleration = curAcceleration;
9549
9550 // hook_setreturnregisters(®s);
9551 if (outStation != nullptr)
9552 *outStation = _vehicleStationIndex;
9553 return _vehicleMotionTrackFlags;
9554 }
9555
GetRideEntry() const9556 rct_ride_entry* Vehicle::GetRideEntry() const
9557 {
9558 return get_ride_entry(ride_subtype);
9559 }
9560
Entry() const9561 rct_ride_entry_vehicle* Vehicle::Entry() const
9562 {
9563 rct_ride_entry* rideEntry = GetRideEntry();
9564 if (rideEntry == nullptr)
9565 {
9566 return nullptr;
9567 }
9568 return &rideEntry->vehicles[vehicle_type];
9569 }
9570
GetRide() const9571 Ride* Vehicle::GetRide() const
9572 {
9573 return get_ride(ride);
9574 }
9575
NumPeepsUntilTrainTail() const9576 int32_t Vehicle::NumPeepsUntilTrainTail() const
9577 {
9578 int32_t numPeeps = 0;
9579 for (const Vehicle* vehicle = GetEntity<Vehicle>(sprite_index); vehicle != nullptr;
9580 vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
9581 {
9582 numPeeps += vehicle->num_peeps;
9583 }
9584
9585 return numPeeps;
9586 }
9587
9588 /**
9589 *
9590 * rct2: 0x006DA1EC
9591 */
InvalidateWindow()9592 void Vehicle::InvalidateWindow()
9593 {
9594 auto intent = Intent(INTENT_ACTION_INVALIDATE_VEHICLE_WINDOW);
9595 intent.putExtra(INTENT_EXTRA_VEHICLE, this);
9596 context_broadcast_intent(&intent);
9597 }
9598
UpdateCrossings() const9599 void Vehicle::UpdateCrossings() const
9600 {
9601 if (TrainHead() != this)
9602 {
9603 return;
9604 }
9605
9606 const Vehicle* frontVehicle{};
9607 const Vehicle* backVehicle{};
9608
9609 bool travellingForwards = !HasUpdateFlag(VEHICLE_UPDATE_FLAG_REVERSING_SHUTTLE);
9610
9611 if (travellingForwards)
9612 {
9613 frontVehicle = this;
9614 backVehicle = TrainTail();
9615 }
9616 else
9617 {
9618 frontVehicle = TrainTail();
9619 backVehicle = this;
9620 }
9621
9622 track_begin_end output{};
9623 int32_t direction{};
9624
9625 CoordsXYE xyElement = { frontVehicle->TrackLocation,
9626 map_get_track_element_at_of_type_seq(
9627 frontVehicle->TrackLocation, frontVehicle->GetTrackType(), 0) };
9628 int32_t curZ = frontVehicle->TrackLocation.z;
9629
9630 if (xyElement.element != nullptr && status != Vehicle::Status::Arriving)
9631 {
9632 int16_t autoReserveAhead = 4 + abs(velocity) / 150000;
9633 int16_t crossingBonus = 0;
9634 bool playedClaxon = false;
9635
9636 // vehicle positions mean we have to take larger
9637 // margins for travelling backwards
9638 if (!travellingForwards)
9639 {
9640 autoReserveAhead += 1;
9641 }
9642
9643 while (true)
9644 {
9645 auto* pathElement = map_get_path_element_at(TileCoordsXYZ(CoordsXYZ{ xyElement, xyElement.element->GetBaseZ() }));
9646 auto curRide = GetRide();
9647
9648 // Many New Element parks have invisible rides hacked into the path.
9649 // Limit path blocking to rides actually supporting level crossings to prevent peeps getting stuck everywhere.
9650 if (pathElement != nullptr && curRide != nullptr
9651 && GetRideTypeDescriptor(curRide->type).HasFlag(RIDE_TYPE_FLAG_SUPPORTS_LEVEL_CROSSINGS))
9652 {
9653 if (!playedClaxon && !pathElement->IsBlockedByVehicle())
9654 {
9655 Claxon();
9656 }
9657 crossingBonus = 4;
9658 pathElement->SetIsBlockedByVehicle(true);
9659 }
9660 else
9661 {
9662 crossingBonus = 0;
9663 }
9664
9665 if (--autoReserveAhead + crossingBonus <= 0)
9666 {
9667 break;
9668 }
9669
9670 curZ = xyElement.element->base_height;
9671
9672 if (travellingForwards)
9673 {
9674 if (!track_block_get_next(&xyElement, &xyElement, &curZ, &direction))
9675 {
9676 break;
9677 }
9678 }
9679 else
9680 {
9681 if (!track_block_get_previous(xyElement, &output))
9682 {
9683 break;
9684 }
9685 xyElement.x = output.begin_x;
9686 xyElement.y = output.begin_y;
9687 xyElement.element = output.begin_element;
9688 }
9689
9690 if (xyElement.element->AsTrack()->IsStation())
9691 {
9692 break;
9693 }
9694 }
9695 }
9696
9697 xyElement = { backVehicle->TrackLocation,
9698 map_get_track_element_at_of_type_seq(backVehicle->TrackLocation, backVehicle->GetTrackType(), 0) };
9699 curZ = backVehicle->TrackLocation.z;
9700
9701 if (xyElement.element != nullptr)
9702 {
9703 uint8_t freeCount = travellingForwards ? 3 : 1;
9704
9705 while (freeCount-- > 0)
9706 {
9707 if (travellingForwards)
9708 {
9709 if (track_block_get_previous(xyElement, &output))
9710 {
9711 xyElement.x = output.begin_x;
9712 xyElement.y = output.begin_y;
9713 xyElement.element = output.begin_element;
9714 }
9715 }
9716
9717 auto* pathElement = map_get_path_element_at(TileCoordsXYZ(CoordsXYZ{ xyElement, xyElement.element->GetBaseZ() }));
9718 if (pathElement != nullptr)
9719 {
9720 pathElement->SetIsBlockedByVehicle(false);
9721 }
9722 }
9723 }
9724 }
9725
Claxon() const9726 void Vehicle::Claxon() const
9727 {
9728 rct_ride_entry* rideEntry = GetRideEntry();
9729 switch (rideEntry->vehicles[vehicle_type].sound_range)
9730 {
9731 case SOUND_RANGE_WHISTLE:
9732 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::TrainWhistle, { x, y, z });
9733 break;
9734 case SOUND_RANGE_BELL:
9735 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::Tram, { x, y, z });
9736 break;
9737 }
9738 }
9739
GetHead()9740 Vehicle* Vehicle::GetHead()
9741 {
9742 auto v = this;
9743 while (v != nullptr && !v->IsHead())
9744 {
9745 v = GetEntity<Vehicle>(v->prev_vehicle_on_ride);
9746 }
9747 return v;
9748 }
9749
GetHead() const9750 const Vehicle* Vehicle::GetHead() const
9751 {
9752 return (const_cast<Vehicle*>(this)->GetHead());
9753 }
9754
GetCar(size_t carIndex) const9755 Vehicle* Vehicle::GetCar(size_t carIndex) const
9756 {
9757 auto car = const_cast<Vehicle*>(this);
9758 for (; carIndex != 0; carIndex--)
9759 {
9760 car = GetEntity<Vehicle>(car->next_vehicle_on_train);
9761 if (car == nullptr)
9762 {
9763 log_error("Tried to get non-existent car from index!");
9764 return nullptr;
9765 }
9766 }
9767 return car;
9768 }
9769
SetState(Vehicle::Status vehicleStatus,uint8_t subState)9770 void Vehicle::SetState(Vehicle::Status vehicleStatus, uint8_t subState)
9771 {
9772 status = vehicleStatus;
9773 sub_state = subState;
9774 InvalidateWindow();
9775 }
9776
IsGhost() const9777 bool Vehicle::IsGhost() const
9778 {
9779 auto r = GetRide();
9780 return r != nullptr && r->status == RideStatus::Simulating;
9781 }
9782
EnableCollisionsForTrain()9783 void Vehicle::EnableCollisionsForTrain()
9784 {
9785 assert(this->IsHead());
9786 for (auto vehicle = this; vehicle != nullptr; vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train))
9787 {
9788 vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_COLLISION_DISABLED);
9789 }
9790 }
9791