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 "Station.h"
11
12 #include "../Game.h"
13 #include "../peep/Guest.h"
14 #include "../scenario/Scenario.h"
15 #include "../world/Location.hpp"
16 #include "Track.h"
17 #include "Vehicle.h"
18
19 static void ride_update_station_blocksection(Ride* ride, StationIndex stationIndex);
20 static void ride_update_station_dodgems(Ride* ride, StationIndex stationIndex);
21 static void ride_update_station_normal(Ride* ride, StationIndex stationIndex);
22 static void ride_update_station_race(Ride* ride, StationIndex stationIndex);
23 static void ride_race_init_vehicle_speeds(Ride* ride);
24 static void ride_invalidate_station_start(Ride* ride, StationIndex stationIndex, bool greenLight);
25
26 /**
27 *
28 * rct2: 0x006ABFFB
29 */
ride_update_station(Ride * ride,StationIndex stationIndex)30 void ride_update_station(Ride* ride, StationIndex stationIndex)
31 {
32 if (ride->stations[stationIndex].Start.IsNull())
33 return;
34
35 switch (ride->mode)
36 {
37 case RideMode::Race:
38 ride_update_station_race(ride, stationIndex);
39 break;
40 case RideMode::Dodgems:
41 ride_update_station_dodgems(ride, stationIndex);
42 break;
43 case RideMode::ContinuousCircuitBlockSectioned:
44 case RideMode::PoweredLaunchBlockSectioned:
45 ride_update_station_blocksection(ride, stationIndex);
46 break;
47 default:
48 ride_update_station_normal(ride, stationIndex);
49 break;
50 }
51 }
52
53 /**
54 *
55 * rct2: 0x006AC0A1
56 */
ride_update_station_blocksection(Ride * ride,StationIndex stationIndex)57 static void ride_update_station_blocksection(Ride* ride, StationIndex stationIndex)
58 {
59 TileElement* tileElement = ride_get_station_start_track_element(ride, stationIndex);
60
61 if ((ride->status == RideStatus::Closed && ride->num_riders == 0)
62 || (tileElement != nullptr && tileElement->AsTrack()->BlockBrakeClosed()))
63 {
64 ride->stations[stationIndex].Depart &= ~STATION_DEPART_FLAG;
65
66 if ((ride->stations[stationIndex].Depart & STATION_DEPART_FLAG)
67 || (tileElement != nullptr && tileElement->AsTrack()->HasGreenLight()))
68 ride_invalidate_station_start(ride, stationIndex, false);
69 }
70 else
71 {
72 if (!(ride->stations[stationIndex].Depart & STATION_DEPART_FLAG))
73 {
74 ride->stations[stationIndex].Depart |= STATION_DEPART_FLAG;
75 ride_invalidate_station_start(ride, stationIndex, true);
76 }
77 else if (tileElement != nullptr && tileElement->AsTrack()->HasGreenLight())
78 {
79 ride_invalidate_station_start(ride, stationIndex, true);
80 }
81 }
82 }
83
84 /**
85 *
86 * rct2: 0x006AC12B
87 */
ride_update_station_dodgems(Ride * ride,StationIndex stationIndex)88 static void ride_update_station_dodgems(Ride* ride, StationIndex stationIndex)
89 {
90 // Change of station depart flag should really call invalidate_station_start
91 // but since dodgems do not have station lights there is no point.
92 if (ride->status == RideStatus::Closed || (ride->lifecycle_flags & (RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED)))
93 {
94 ride->stations[stationIndex].Depart &= ~STATION_DEPART_FLAG;
95 return;
96 }
97
98 if (ride->lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING)
99 {
100 int32_t dx = ride->time_limit * 32;
101 int32_t dh = (dx >> 8) & 0xFF;
102 for (size_t i = 0; i < ride->num_vehicles; i++)
103 {
104 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[i]);
105 if (vehicle == nullptr)
106 continue;
107
108 if (vehicle->var_CE < dh)
109 continue;
110
111 // End match
112 ride->lifecycle_flags &= ~RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING;
113 ride->stations[stationIndex].Depart &= ~STATION_DEPART_FLAG;
114 return;
115 }
116
117 // Continue match
118 ride->stations[stationIndex].Depart |= STATION_DEPART_FLAG;
119 }
120 else
121 {
122 // Check if all vehicles are ready to go
123 for (size_t i = 0; i < ride->num_vehicles; i++)
124 {
125 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[i]);
126 if (vehicle == nullptr)
127 continue;
128
129 if (vehicle->status != Vehicle::Status::WaitingToDepart)
130 {
131 ride->stations[stationIndex].Depart &= ~STATION_DEPART_FLAG;
132 return;
133 }
134 }
135
136 // Begin the match
137 ride->lifecycle_flags |= RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING;
138 ride->stations[stationIndex].Depart |= STATION_DEPART_FLAG;
139 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST;
140 }
141 }
142
143 /**
144 *
145 * rct2: 0x006AC02C
146 */
ride_update_station_normal(Ride * ride,StationIndex stationIndex)147 static void ride_update_station_normal(Ride* ride, StationIndex stationIndex)
148 {
149 int32_t time = ride->stations[stationIndex].Depart & STATION_DEPART_MASK;
150 if ((ride->lifecycle_flags & (RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED))
151 || (ride->status == RideStatus::Closed && ride->num_riders == 0))
152 {
153 if (time != 0 && time != 127 && !(gCurrentTicks & 7))
154 time--;
155
156 ride->stations[stationIndex].Depart = time;
157 ride_invalidate_station_start(ride, stationIndex, false);
158 }
159 else
160 {
161 if (time == 0)
162 {
163 ride->stations[stationIndex].Depart |= STATION_DEPART_FLAG;
164 ride_invalidate_station_start(ride, stationIndex, true);
165 }
166 else
167 {
168 if (time != 127 && !(gCurrentTicks & 31))
169 time--;
170
171 ride->stations[stationIndex].Depart = time;
172 ride_invalidate_station_start(ride, stationIndex, false);
173 }
174 }
175 }
176
177 /**
178 *
179 * rct2: 0x006AC1DF
180 */
ride_update_station_race(Ride * ride,StationIndex stationIndex)181 static void ride_update_station_race(Ride* ride, StationIndex stationIndex)
182 {
183 if (ride->status == RideStatus::Closed || (ride->lifecycle_flags & (RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED)))
184 {
185 if (ride->stations[stationIndex].Depart & STATION_DEPART_FLAG)
186 {
187 ride->stations[stationIndex].Depart &= ~STATION_DEPART_FLAG;
188 ride_invalidate_station_start(ride, stationIndex, false);
189 }
190 return;
191 }
192
193 if (ride->lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING)
194 {
195 int32_t numLaps = ride->num_laps;
196
197 for (size_t i = 0; i < ride->num_vehicles; i++)
198 {
199 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[i]);
200 if (vehicle == nullptr)
201 continue;
202
203 if (vehicle->status != Vehicle::Status::WaitingToDepart && vehicle->num_laps >= numLaps)
204 {
205 // Found a winner
206 if (vehicle->num_peeps != 0)
207 {
208 auto* peep = GetEntity<Guest>(vehicle->peep[0]);
209 if (peep != nullptr)
210 {
211 ride->race_winner = peep->sprite_index;
212 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST;
213 }
214 }
215
216 // Race is over
217 ride->lifecycle_flags &= ~RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING;
218 if (ride->stations[stationIndex].Depart & STATION_DEPART_FLAG)
219 {
220 ride->stations[stationIndex].Depart &= ~STATION_DEPART_FLAG;
221 ride_invalidate_station_start(ride, stationIndex, false);
222 }
223 return;
224 }
225 }
226
227 // Continue racing
228 ride->stations[stationIndex].Depart |= STATION_DEPART_FLAG;
229 }
230 else
231 {
232 // Check if all vehicles are ready to go
233 for (size_t i = 0; i < ride->num_vehicles; i++)
234 {
235 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[i]);
236 if (vehicle == nullptr)
237 continue;
238
239 if (vehicle->status != Vehicle::Status::WaitingToDepart && vehicle->status != Vehicle::Status::Departing)
240 {
241 if (ride->stations[stationIndex].Depart & STATION_DEPART_FLAG)
242 {
243 ride->stations[stationIndex].Depart &= ~STATION_DEPART_FLAG;
244 ride_invalidate_station_start(ride, stationIndex, false);
245 }
246 return;
247 }
248 }
249
250 // Begin the race
251 ride_race_init_vehicle_speeds(ride);
252 ride->lifecycle_flags |= RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING;
253 if (!(ride->stations[stationIndex].Depart & STATION_DEPART_FLAG))
254 {
255 ride->stations[stationIndex].Depart |= STATION_DEPART_FLAG;
256 ride_invalidate_station_start(ride, stationIndex, true);
257 }
258 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST;
259 }
260 }
261
262 /**
263 *
264 * rct2: 0x006AC988
265 * set the speed of the go kart type vehicle at the start to a random value or alter if peep name is an easter egg
266 * @param ride (esi)
267 */
ride_race_init_vehicle_speeds(Ride * ride)268 static void ride_race_init_vehicle_speeds(Ride* ride)
269 {
270 for (size_t i = 0; i < ride->num_vehicles; i++)
271 {
272 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[i]);
273 if (vehicle == nullptr)
274 continue;
275
276 vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_6);
277
278 rct_ride_entry* rideEntry = vehicle->GetRideEntry();
279
280 vehicle->speed = (scenario_rand() & 16) - 8 + rideEntry->vehicles[vehicle->vehicle_type].powered_max_speed;
281
282 if (vehicle->num_peeps != 0)
283 {
284 auto* guest = GetEntity<Guest>(vehicle->peep[0]);
285
286 // Easter egg names should only work on guests
287 if (guest != nullptr)
288 {
289 switch (guest->GetEasterEggNameId())
290 {
291 case EASTEREGG_PEEP_NAME_MICHAEL_SCHUMACHER:
292 vehicle->speed += 35;
293 break;
294 case EASTEREGG_PEEP_NAME_JACQUES_VILLENEUVE:
295 vehicle->speed += 25;
296 break;
297 case EASTEREGG_PEEP_NAME_DAMON_HILL:
298 vehicle->speed += 55;
299 break;
300 case EASTEREGG_PEEP_NAME_CHRIS_SAWYER:
301 vehicle->speed += 14;
302 break;
303 case EASTEREGG_PEEP_NAME_MR_BEAN:
304 vehicle->speed = 9;
305 break;
306 }
307 }
308 }
309 }
310 }
311
312 /**
313 *
314 * rct2: 0x006AC2C7
315 */
ride_invalidate_station_start(Ride * ride,StationIndex stationIndex,bool greenLight)316 static void ride_invalidate_station_start(Ride* ride, StationIndex stationIndex, bool greenLight)
317 {
318 auto startPos = ride->stations[stationIndex].Start;
319 TileElement* tileElement = ride_get_station_start_track_element(ride, stationIndex);
320
321 // If no station track found return
322 if (tileElement == nullptr)
323 return;
324
325 tileElement->AsTrack()->SetHasGreenLight(greenLight);
326
327 // Invalidate map tile
328 map_invalidate_tile_zoom1({ startPos, tileElement->GetBaseZ(), tileElement->GetClearanceZ() });
329 }
330
ride_get_station_start_track_element(const Ride * ride,StationIndex stationIndex)331 TileElement* ride_get_station_start_track_element(const Ride* ride, StationIndex stationIndex)
332 {
333 auto stationStart = ride->stations[stationIndex].GetStart();
334
335 // Find the station track element
336 TileElement* tileElement = map_get_first_element_at(stationStart);
337 if (tileElement == nullptr)
338 return nullptr;
339 do
340 {
341 if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK && stationStart.z == tileElement->GetBaseZ())
342 return tileElement;
343
344 } while (!(tileElement++)->IsLastForTile());
345
346 return nullptr;
347 }
348
ride_get_station_exit_element(const CoordsXYZ & elementPos)349 TileElement* ride_get_station_exit_element(const CoordsXYZ& elementPos)
350 {
351 // Find the station track element
352 TileElement* tileElement = map_get_first_element_at(elementPos);
353 if (tileElement == nullptr)
354 return nullptr;
355 do
356 {
357 if (tileElement == nullptr)
358 break;
359 if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE && elementPos.z == tileElement->GetBaseZ())
360 return tileElement;
361 } while (!(tileElement++)->IsLastForTile());
362
363 return nullptr;
364 }
365
ride_get_first_valid_station_exit(Ride * ride)366 StationIndex ride_get_first_valid_station_exit(Ride* ride)
367 {
368 for (StationIndex i = 0; i < MAX_STATIONS; i++)
369 {
370 if (!ride->stations[i].Exit.IsNull())
371 {
372 return i;
373 }
374 }
375 return STATION_INDEX_NULL;
376 }
377
ride_get_first_valid_station_start(const Ride * ride)378 StationIndex ride_get_first_valid_station_start(const Ride* ride)
379 {
380 for (StationIndex i = 0; i < MAX_STATIONS; i++)
381 {
382 if (!ride->stations[i].Start.IsNull())
383 {
384 return i;
385 }
386 }
387 return STATION_INDEX_NULL;
388 }
389
ride_get_first_empty_station_start(const Ride * ride)390 StationIndex ride_get_first_empty_station_start(const Ride* ride)
391 {
392 for (StationIndex i = 0; i < MAX_STATIONS; i++)
393 {
394 if (ride->stations[i].Start.IsNull())
395 {
396 return i;
397 }
398 }
399 return STATION_INDEX_NULL;
400 }
401
ride_get_entrance_location(const Ride * ride,const StationIndex stationIndex)402 TileCoordsXYZD ride_get_entrance_location(const Ride* ride, const StationIndex stationIndex)
403 {
404 return ride->stations[stationIndex].Entrance;
405 }
406
ride_get_exit_location(const Ride * ride,const StationIndex stationIndex)407 TileCoordsXYZD ride_get_exit_location(const Ride* ride, const StationIndex stationIndex)
408 {
409 return ride->stations[stationIndex].Exit;
410 }
411
ride_clear_entrance_location(Ride * ride,const StationIndex stationIndex)412 void ride_clear_entrance_location(Ride* ride, const StationIndex stationIndex)
413 {
414 ride->stations[stationIndex].Entrance.SetNull();
415 }
416
ride_clear_exit_location(Ride * ride,const StationIndex stationIndex)417 void ride_clear_exit_location(Ride* ride, const StationIndex stationIndex)
418 {
419 ride->stations[stationIndex].Exit.SetNull();
420 }
421
ride_set_entrance_location(Ride * ride,const StationIndex stationIndex,const TileCoordsXYZD & location)422 void ride_set_entrance_location(Ride* ride, const StationIndex stationIndex, const TileCoordsXYZD& location)
423 {
424 ride->stations[stationIndex].Entrance = location;
425 }
426
ride_set_exit_location(Ride * ride,const StationIndex stationIndex,const TileCoordsXYZD & location)427 void ride_set_exit_location(Ride* ride, const StationIndex stationIndex, const TileCoordsXYZD& location)
428 {
429 ride->stations[stationIndex].Exit = location;
430 }
431
GetBaseZ() const432 int32_t RideStation::GetBaseZ() const
433 {
434 return Height * COORDS_Z_STEP;
435 }
436
SetBaseZ(int32_t newZ)437 void RideStation::SetBaseZ(int32_t newZ)
438 {
439 Height = newZ / COORDS_Z_STEP;
440 }
441
GetStart() const442 CoordsXYZ RideStation::GetStart() const
443 {
444 return { Start, GetBaseZ() };
445 }
446