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 "Guest.h"
11
12 #include "../Context.h"
13 #include "../Game.h"
14 #include "../OpenRCT2.h"
15 #include "../audio/audio.h"
16 #include "../config/Config.h"
17 #include "../core/Guard.hpp"
18 #include "../core/Numerics.hpp"
19 #include "../interface/Window_internal.h"
20 #include "../localisation/Localisation.h"
21 #include "../management/Finance.h"
22 #include "../management/Marketing.h"
23 #include "../management/NewsItem.h"
24 #include "../network/network.h"
25 #include "../rct2/RCT2.h"
26 #include "../ride/Ride.h"
27 #include "../ride/RideData.h"
28 #include "../ride/ShopItem.h"
29 #include "../ride/Station.h"
30 #include "../ride/Track.h"
31 #include "../ride/Vehicle.h"
32 #include "../scenario/Scenario.h"
33 #include "../scripting/HookEngine.h"
34 #include "../scripting/ScriptEngine.h"
35 #include "../util/Math.hpp"
36 #include "../windows/Intent.h"
37 #include "../world/Balloon.h"
38 #include "../world/Climate.h"
39 #include "../world/Footpath.h"
40 #include "../world/LargeScenery.h"
41 #include "../world/Map.h"
42 #include "../world/MoneyEffect.h"
43 #include "../world/Park.h"
44 #include "../world/Particle.h"
45 #include "../world/Scenery.h"
46 #include "../world/Sprite.h"
47 #include "../world/Surface.h"
48 #include "../world/TileElementsView.h"
49 #include "GuestPathfinding.h"
50 #include "Peep.h"
51 #include "RideUseSystem.h"
52 #include "Staff.h"
53
54 #include <algorithm>
55 #include <functional>
56 #include <iterator>
57
58 using namespace OpenRCT2;
59
60 // Locations of the spiral slide platform that a peep walks from the entrance of the ride to the
61 // entrance of the slide. Up to 4 waypoints for each 4 sides that an ride entrance can be located
62 // and 4 different rotations of the ride. 4 * 4 * 4 = 64 locations.
63 // clang-format off
64 static constexpr const CoordsXY SpiralSlideWalkingPath[64] = {
65 { 56, 8 },
66 { 8, 8 },
67 { 8, 32 },
68 { 32, 32 },
69 { 8, 8 },
70 { 8, 8 },
71 { 8, 32 },
72 { 32, 32 },
73 { 8, 32 },
74 { 8, 32 },
75 { 8, 32 },
76 { 32, 32 },
77 { 8, 56 },
78 { 8, 32 },
79 { 8, 32 },
80 { 32, 32 },
81 { 56, 24 },
82 { 32, 24 },
83 { 32, 24 },
84 { 32, 0 },
85 { 56, -24 },
86 { 56, 24 },
87 { 32, 24 },
88 { 32, 0 },
89 { 8, 24 },
90 { 32, 24 },
91 { 32, 24 },
92 { 32, 0 },
93 { 32, 24 },
94 { 32, 24 },
95 { 32, 24 },
96 { 32, 0 },
97 { 24, 0 },
98 { 24, 0 },
99 { 24, 0 },
100 { 0, 0 },
101 { 24, -24 },
102 { 24, 0 },
103 { 24, 0 },
104 { 0, 0 },
105 { -24, -24 },
106 { 24, -24 },
107 { 24, 0 },
108 { 0, 0 },
109 { 24, 24 },
110 { 24, 0 },
111 { 24, 0 },
112 { 0, 0 },
113 { 24, 8 },
114 { 0, 8 },
115 { 0, 8 },
116 { 0, 32 },
117 { 0, 8 },
118 { 0, 8 },
119 { 0, 8 },
120 { 0, 32 },
121 { -24, 8 },
122 { 0, 8 },
123 { 0, 8 },
124 { 0, 32 },
125 { -24, 56 },
126 { -24, 8 },
127 { 0, 8 },
128 { 0, 32 },
129 };
130
131 /** rct2: 0x00981F4C, 0x00981F4E */
132 static constexpr const CoordsXY _WatchingPositionOffsets[] = {
133 { 7, 5 },
134 { 5, 25 },
135 { 25, 5 },
136 { 5, 7 },
137 { 7, 9 },
138 { 9, 25 },
139 { 25, 9 },
140 { 9, 7 },
141 { 7, 23 },
142 { 23, 25 },
143 { 25, 23 },
144 { 23, 7 },
145 { 7, 27 },
146 { 27, 25 },
147 { 25, 27 },
148 { 27, 7 },
149 { 7, 0 },
150 { 0, 25 },
151 { 25, 0 },
152 { 0, 7 },
153 { 7, 0 },
154 { 0, 25 },
155 { 25, 0 },
156 { 0, 7 },
157 { 7, 0 },
158 { 0, 25 },
159 { 25, 0 },
160 { 0, 7 },
161 { 7, 0 },
162 { 0, 25 },
163 { 25, 0 },
164 { 0, 7 },
165 };
166
167 static constexpr const ride_rating NauseaMaximumThresholds[] = {
168 300,
169 600,
170 800,
171 1000,
172 };
173
174 /** rct2: 009823AC */
175 static constexpr const PeepThoughtType crowded_thoughts[] = {
176 PeepThoughtType::Lost,
177 PeepThoughtType::Tired,
178 PeepThoughtType::BadLitter,
179 PeepThoughtType::Hungry,
180 PeepThoughtType::Thirsty,
181 PeepThoughtType::VeryClean,
182 PeepThoughtType::Crowded,
183 PeepThoughtType::Scenery,
184 PeepThoughtType::VeryClean,
185 PeepThoughtType::Music,
186 PeepThoughtType::Watched,
187 PeepThoughtType::NotHungry,
188 PeepThoughtType::NotThirsty,
189 PeepThoughtType::Toilet,
190 PeepThoughtType::None,
191 PeepThoughtType::None,
192 };
193
194 static constexpr const char *gPeepEasterEggNames[] = {
195 "MICHAEL SCHUMACHER",
196 "JACQUES VILLENEUVE",
197 "DAMON HILL",
198 "MR BEAN",
199 "CHRIS SAWYER",
200 "KATIE BRAYSHAW",
201 "MELANIE WARN",
202 "SIMON FOSTER",
203 "JOHN WARDLEY",
204 "LISA STIRLING",
205 "DONALD MACRAE",
206 "KATHERINE MCGOWAN",
207 "FRANCES MCGOWAN",
208 "CORINA MASSOURA",
209 "CAROL YOUNG",
210 "MIA SHERIDAN",
211 "KATIE RODGER",
212 "EMMA GARRELL",
213 "JOANNE BARTON",
214 "FELICITY ANDERSON",
215 "KATIE SMITH",
216 "EILIDH BELL",
217 "NANCY STILLWAGON",
218 "DAVID ELLIS",
219 };
220 // clang-format on
221
222 // Flags used by PeepThoughtToActionMap
223 enum PeepThoughtToActionFlag : uint8_t
224 {
225 PEEP_THOUGHT_ACTION_NO_FLAGS = 0,
226 PEEP_THOUGHT_ACTION_FLAG_RIDE = (1 << 0),
227 PEEP_THOUGHT_ACTION_FLAG_SHOP_ITEM_SINGULAR = (1 << 1),
228 PEEP_THOUGHT_ACTION_FLAG_SHOP_ITEM_INDEFINITE = (1 << 2),
229 };
230
231 /** rct2: 0x00981DB0 */
232 static struct
233 {
234 PeepActionType action;
235 PeepThoughtToActionFlag flags;
236 } PeepThoughtToActionMap[] = {
237 { PeepActionType::ShakeHead, PEEP_THOUGHT_ACTION_FLAG_RIDE },
238 { PeepActionType::EmptyPockets, PEEP_THOUGHT_ACTION_NO_FLAGS },
239 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
240 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
241 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
242 { PeepActionType::Wow, PEEP_THOUGHT_ACTION_FLAG_RIDE },
243 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_SHOP_ITEM_SINGULAR },
244 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
245 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
246 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
247 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
248 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_SHOP_ITEM_INDEFINITE },
249 { PeepActionType::ShakeHead, PEEP_THOUGHT_ACTION_FLAG_SHOP_ITEM_INDEFINITE },
250 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
251 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
252 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
253 { PeepActionType::Wave, PEEP_THOUGHT_ACTION_NO_FLAGS },
254 { PeepActionType::Joy, PEEP_THOUGHT_ACTION_FLAG_RIDE },
255 { PeepActionType::CheckTime, PEEP_THOUGHT_ACTION_FLAG_RIDE },
256 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
257 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
258 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
259 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
260 { PeepActionType::Wave, PEEP_THOUGHT_ACTION_FLAG_RIDE },
261 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
262 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
263 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
264 { PeepActionType::Wave, PEEP_THOUGHT_ACTION_NO_FLAGS },
265 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
266 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
267 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
268 { PeepActionType::Disgust, PEEP_THOUGHT_ACTION_NO_FLAGS },
269 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
270 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
271 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
272 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
273 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
274 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
275 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
276 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
277 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
278 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
279 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
280 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
281 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
282 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
283 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
284 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
285 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
286 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
287 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
288 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
289 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
290 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
291 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
292 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
293 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
294 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
295 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
296 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
297 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
298 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
299 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
300 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
301 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
302 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
303 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
304 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
305 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
306 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
307 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
308 { PeepActionType::BeingWatched, PEEP_THOUGHT_ACTION_NO_FLAGS },
309 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
310 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
311 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
312 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
313 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
314 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
315 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
316 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
317 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
318 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
319 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
320 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
321 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
322 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
323 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
324 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
325 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
326 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
327 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
328 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
329 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
330 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
331 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
332 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
333 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
334 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
335 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
336 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
337 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
338 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
339 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
340 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
341 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
342 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
343 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
344 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
345 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
346 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
347 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
348 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
349 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
350 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
351 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
352 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
353 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
354 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
355 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
356 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
357 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
358 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
359 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
360 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
361 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
362 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
363 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
364 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
365 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
366 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
367 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
368 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
369 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
370 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
371 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
372 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
373 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
374 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
375 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
376 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
377 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
378 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
379 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
380 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
381 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
382 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
383 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
384 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
385 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
386 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
387 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
388 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
389 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
390 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
391 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
392 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
393 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
394 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
395 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
396 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
397 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
398 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
399 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
400 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
401 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
402 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
403 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
404 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
405 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
406 { PeepActionType::ShakeHead, PEEP_THOUGHT_ACTION_NO_FLAGS },
407 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
408 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_NO_FLAGS },
409 { PeepActionType::Joy, PEEP_THOUGHT_ACTION_NO_FLAGS },
410 { PeepActionType::Walking, PEEP_THOUGHT_ACTION_FLAG_RIDE },
411 };
412
413 // These arrays contain the base minimum and maximum nausea ratings for peeps, based on their nausea tolerance level.
414 static constexpr const ride_rating NauseaMinimumThresholds[] = {
415 0,
416 0,
417 200,
418 400,
419 };
420
421 static bool peep_has_voucher_for_free_ride(Guest* peep, Ride* ride);
422 static void peep_ride_is_too_intense(Guest* peep, Ride* ride, bool peepAtRide);
423 static void peep_reset_ride_heading(Guest* peep);
424 static void peep_tried_to_enter_full_queue(Guest* peep, Ride* ride);
425 static int16_t peep_calculate_ride_satisfaction(Guest* peep, Ride* ride);
426 static void peep_update_favourite_ride(Guest* peep, Ride* ride);
427 static int16_t peep_calculate_ride_value_satisfaction(Guest* peep, Ride* ride);
428 static int16_t peep_calculate_ride_intensity_nausea_satisfaction(Guest* peep, Ride* ride);
429 static void peep_update_ride_nausea_growth(Guest* peep, Ride* ride);
430 static bool peep_should_go_on_ride_again(Guest* peep, Ride* ride);
431 static bool peep_should_preferred_intensity_increase(Guest* peep);
432 static bool peep_really_liked_ride(Guest* peep, Ride* ride);
433 static PeepThoughtType peep_assess_surroundings(int16_t centre_x, int16_t centre_y, int16_t centre_z);
434 static void peep_update_hunger(Guest* peep);
435 static void peep_decide_whether_to_leave_park(Guest* peep);
436 static void peep_leave_park(Guest* peep);
437 static void peep_head_for_nearest_ride_type(Guest* peep, int32_t rideType);
438 static void peep_head_for_nearest_ride_with_flags(Guest* peep, int32_t rideTypeFlags);
439 bool loc_690FD0(Peep* peep, ride_id_t* rideToView, uint8_t* rideSeatToView, TileElement* tileElement);
440
Is() const441 template<> bool EntityBase::Is<Guest>() const
442 {
443 return Type == EntityType::Guest;
444 }
445
IsValidLocation(const CoordsXYZ & coords)446 static bool IsValidLocation(const CoordsXYZ& coords)
447 {
448 if (coords.x != LOCATION_NULL)
449 {
450 if (map_is_location_valid(coords))
451 {
452 return true;
453 }
454 }
455
456 return false;
457 }
458
ApplyEasterEggToNearbyGuests(Guest * guest)459 template<void (Guest::*EasterEggFunc)(Guest*), bool applyToSelf> static void ApplyEasterEggToNearbyGuests(Guest* guest)
460 {
461 const auto guestLoc = guest->GetLocation();
462 if (!IsValidLocation(guestLoc))
463 return;
464
465 for (auto* otherGuest : EntityTileList<Guest>(guestLoc))
466 {
467 if constexpr (!applyToSelf)
468 {
469 if (otherGuest == guest)
470 {
471 // Can not apply effect on self.
472 continue;
473 }
474 }
475 auto zDiff = std::abs(otherGuest->z - guestLoc.z);
476 if (zDiff <= 32)
477 {
478 std::invoke(EasterEggFunc, *guest, otherGuest);
479 }
480 }
481 }
482
GivePassingPeepsPurpleClothes(Guest * passingPeep)483 void Guest::GivePassingPeepsPurpleClothes(Guest* passingPeep)
484 {
485 passingPeep->TshirtColour = COLOUR_BRIGHT_PURPLE;
486 passingPeep->TrousersColour = COLOUR_BRIGHT_PURPLE;
487 passingPeep->Invalidate();
488 }
489
GivePassingPeepsPizza(Guest * passingPeep)490 void Guest::GivePassingPeepsPizza(Guest* passingPeep)
491 {
492 if (passingPeep->HasItem(ShopItem::Pizza))
493 return;
494
495 passingPeep->GiveItem(ShopItem::Pizza);
496
497 int32_t peepDirection = (sprite_direction >> 3) ^ 2;
498 int32_t otherPeepOppositeDirection = passingPeep->sprite_direction >> 3;
499 if (peepDirection == otherPeepOppositeDirection)
500 {
501 if (passingPeep->IsActionInterruptable())
502 {
503 passingPeep->Action = PeepActionType::Wave2;
504 passingPeep->ActionFrame = 0;
505 passingPeep->ActionSpriteImageOffset = 0;
506 passingPeep->UpdateCurrentActionSpriteType();
507 }
508 }
509 }
510
MakePassingPeepsSick(Guest * passingPeep)511 void Guest::MakePassingPeepsSick(Guest* passingPeep)
512 {
513 if (passingPeep->State != PeepState::Walking)
514 return;
515
516 if (passingPeep->IsActionInterruptable())
517 {
518 passingPeep->Action = PeepActionType::ThrowUp;
519 passingPeep->ActionFrame = 0;
520 passingPeep->ActionSpriteImageOffset = 0;
521 passingPeep->UpdateCurrentActionSpriteType();
522 }
523 }
524
GivePassingPeepsIceCream(Guest * passingPeep)525 void Guest::GivePassingPeepsIceCream(Guest* passingPeep)
526 {
527 if (passingPeep->HasItem(ShopItem::IceCream))
528 return;
529
530 passingPeep->GiveItem(ShopItem::IceCream);
531 passingPeep->UpdateSpriteType();
532 }
533
534 /**
535 *
536 * rct2: 0x0068FD3A
537 */
UpdateEasterEggInteractions()538 void Guest::UpdateEasterEggInteractions()
539 {
540 if (PeepFlags & PEEP_FLAGS_PURPLE)
541 {
542 ApplyEasterEggToNearbyGuests<&Guest::GivePassingPeepsPurpleClothes, true>(this);
543 }
544
545 if (PeepFlags & PEEP_FLAGS_PIZZA)
546 {
547 ApplyEasterEggToNearbyGuests<&Guest::GivePassingPeepsPizza, true>(this);
548 }
549
550 if (PeepFlags & PEEP_FLAGS_CONTAGIOUS)
551 {
552 ApplyEasterEggToNearbyGuests<&Guest::MakePassingPeepsSick, false>(this);
553 }
554
555 if (PeepFlags & PEEP_FLAGS_ICE_CREAM)
556 {
557 ApplyEasterEggToNearbyGuests<&Guest::GivePassingPeepsIceCream, false>(this);
558 }
559
560 if (PeepFlags & PEEP_FLAGS_JOY)
561 {
562 if ((scenario_rand() & 0xFFFF) <= 1456)
563 {
564 if (IsActionInterruptable())
565 {
566 Action = PeepActionType::Joy;
567 ActionFrame = 0;
568 ActionSpriteImageOffset = 0;
569 UpdateCurrentActionSpriteType();
570 }
571 }
572 }
573 }
574
GetEasterEggNameId() const575 int32_t Guest::GetEasterEggNameId() const
576 {
577 char buffer[256]{};
578
579 Formatter ft;
580 FormatNameTo(ft);
581 format_string(buffer, sizeof(buffer), STR_STRINGID, ft.Data());
582
583 for (uint32_t i = 0; i < std::size(gPeepEasterEggNames); i++)
584 if (_stricmp(buffer, gPeepEasterEggNames[i]) == 0)
585 return static_cast<int32_t>(i);
586
587 return -1;
588 }
589
HandleEasterEggName()590 void Guest::HandleEasterEggName()
591 {
592 PeepFlags &= ~PEEP_FLAGS_WAVING;
593 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_KATIE_BRAYSHAW))
594 {
595 PeepFlags |= PEEP_FLAGS_WAVING;
596 }
597
598 PeepFlags &= ~PEEP_FLAGS_PHOTO;
599 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_CHRIS_SAWYER))
600 {
601 PeepFlags |= PEEP_FLAGS_PHOTO;
602 }
603
604 PeepFlags &= ~PEEP_FLAGS_PAINTING;
605 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_SIMON_FOSTER))
606 {
607 PeepFlags |= PEEP_FLAGS_PAINTING;
608 }
609
610 PeepFlags &= ~PEEP_FLAGS_WOW;
611 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_JOHN_WARDLEY))
612 {
613 PeepFlags |= PEEP_FLAGS_WOW;
614 }
615
616 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_MELANIE_WARN))
617 {
618 Happiness = 250;
619 HappinessTarget = 250;
620 Energy = 127;
621 EnergyTarget = 127;
622 Nausea = 0;
623 NauseaTarget = 0;
624 }
625
626 PeepFlags &= ~PEEP_FLAGS_LITTER;
627 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_LISA_STIRLING))
628 {
629 PeepFlags |= PEEP_FLAGS_LITTER;
630 }
631
632 PeepFlags &= ~PEEP_FLAGS_LOST;
633 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_DONALD_MACRAE))
634 {
635 PeepFlags |= PEEP_FLAGS_LOST;
636 }
637
638 PeepFlags &= ~PEEP_FLAGS_HUNGER;
639 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_KATHERINE_MCGOWAN))
640 {
641 PeepFlags |= PEEP_FLAGS_HUNGER;
642 }
643
644 PeepFlags &= ~PEEP_FLAGS_TOILET;
645 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_FRANCES_MCGOWAN))
646 {
647 PeepFlags |= PEEP_FLAGS_TOILET;
648 }
649
650 PeepFlags &= ~PEEP_FLAGS_CROWDED;
651 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_CORINA_MASSOURA))
652 {
653 PeepFlags |= PEEP_FLAGS_CROWDED;
654 }
655
656 PeepFlags &= ~PEEP_FLAGS_HAPPINESS;
657 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_CAROL_YOUNG))
658 {
659 PeepFlags |= PEEP_FLAGS_HAPPINESS;
660 }
661
662 PeepFlags &= ~PEEP_FLAGS_NAUSEA;
663 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_MIA_SHERIDAN))
664 {
665 PeepFlags |= PEEP_FLAGS_NAUSEA;
666 }
667
668 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_KATIE_RODGER))
669 {
670 PeepFlags |= PEEP_FLAGS_LEAVING_PARK;
671 PeepFlags &= ~PEEP_FLAGS_PARK_ENTRANCE_CHOSEN;
672 }
673
674 PeepFlags &= ~PEEP_FLAGS_PURPLE;
675 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_EMMA_GARRELL))
676 {
677 PeepFlags |= PEEP_FLAGS_PURPLE;
678 }
679
680 PeepFlags &= ~PEEP_FLAGS_PIZZA;
681 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_JOANNE_BARTON))
682 {
683 PeepFlags |= PEEP_FLAGS_PIZZA;
684 }
685
686 PeepFlags &= ~PEEP_FLAGS_CONTAGIOUS;
687 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_FELICITY_ANDERSON))
688 {
689 PeepFlags |= PEEP_FLAGS_CONTAGIOUS;
690 }
691
692 PeepFlags &= ~PEEP_FLAGS_JOY;
693 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_KATIE_SMITH))
694 {
695 PeepFlags |= PEEP_FLAGS_JOY;
696 }
697
698 PeepFlags &= ~PEEP_FLAGS_ANGRY;
699 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_EILIDH_BELL))
700 {
701 PeepFlags |= PEEP_FLAGS_ANGRY;
702 }
703
704 PeepFlags &= ~PEEP_FLAGS_ICE_CREAM;
705 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_NANCY_STILLWAGON))
706 {
707 PeepFlags |= PEEP_FLAGS_ICE_CREAM;
708 }
709
710 PeepFlags &= ~PEEP_FLAGS_HERE_WE_ARE;
711 if (CheckEasterEggName(EASTEREGG_PEEP_NAME_DAVID_ELLIS))
712 {
713 PeepFlags |= PEEP_FLAGS_HERE_WE_ARE;
714 }
715 }
716
717 /**
718 *
719 * rct2: 0x0069A5A0
720 * tests if a peep's name matches a cheat code, normally returns using a register flag
721 */
CheckEasterEggName(int32_t index) const722 int32_t Guest::CheckEasterEggName(int32_t index) const
723 {
724 char buffer[256]{};
725
726 Formatter ft;
727 FormatNameTo(ft);
728 format_string(buffer, sizeof(buffer), STR_STRINGID, ft.Data());
729
730 return _stricmp(buffer, gPeepEasterEggNames[index]) == 0;
731 }
732
loc_68F9F3()733 void Guest::loc_68F9F3()
734 {
735 // Idle peep happiness tends towards 127 (50%).
736 if (HappinessTarget >= 128)
737 HappinessTarget--;
738 else
739 HappinessTarget++;
740
741 NauseaTarget = std::max(NauseaTarget - 2, 0);
742
743 if (Energy <= 50)
744 {
745 Energy = std::max(Energy - 2, 0);
746 }
747
748 if (Hunger < 10)
749 {
750 Hunger = std::max(Hunger - 1, 0);
751 }
752
753 if (Thirst < 10)
754 {
755 Thirst = std::max(Thirst - 1, 0);
756 }
757
758 if (Toilet >= 195)
759 {
760 Toilet--;
761 }
762
763 if (State == PeepState::Walking && NauseaTarget >= 128)
764 {
765 if ((scenario_rand() & 0xFF) <= static_cast<uint8_t>((Nausea - 128) / 2))
766 {
767 if (IsActionInterruptable())
768 {
769 Action = PeepActionType::ThrowUp;
770 ActionFrame = 0;
771 ActionSpriteImageOffset = 0;
772 UpdateCurrentActionSpriteType();
773 }
774 }
775 }
776 }
777
loc_68FA89()778 void Guest::loc_68FA89()
779 {
780 // 68FA89
781 if (TimeToConsume == 0 && HasFoodOrDrink())
782 {
783 TimeToConsume += 3;
784 }
785
786 if (TimeToConsume != 0 && State != PeepState::OnRide)
787 {
788 TimeToConsume = std::max(TimeToConsume - 3, 0);
789
790 if (HasDrink())
791 {
792 Thirst = std::min(Thirst + 7, 255);
793 }
794 else
795 {
796 Hunger = std::min(Hunger + 7, 255);
797 Thirst = std::max(Thirst - 3, 0);
798 Toilet = std::min(Toilet + 2, 255);
799 }
800
801 if (TimeToConsume == 0)
802 {
803 int32_t chosen_food = bitscanforward(GetFoodOrDrinkFlags());
804 if (chosen_food != -1)
805 {
806 ShopItem food = ShopItem(chosen_food);
807 RemoveItem(food);
808
809 auto discardContainer = GetShopItemDescriptor(food).DiscardContainer;
810 if (discardContainer != ShopItem::None)
811 {
812 GiveItem(discardContainer);
813 }
814
815 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_INVENTORY;
816 UpdateSpriteType();
817 }
818 }
819 }
820
821 uint8_t newEnergy = Energy;
822 uint8_t newTargetEnergy = EnergyTarget;
823 if (newEnergy >= newTargetEnergy)
824 {
825 newEnergy -= 2;
826 if (newEnergy < newTargetEnergy)
827 newEnergy = newTargetEnergy;
828 }
829 else
830 {
831 newEnergy = std::min(PEEP_MAX_ENERGY_TARGET, newEnergy + 4);
832 if (newEnergy > newTargetEnergy)
833 newEnergy = newTargetEnergy;
834 }
835
836 if (newEnergy < PEEP_MIN_ENERGY)
837 newEnergy = PEEP_MIN_ENERGY;
838
839 /* Previous code here suggested maximum energy is 128. */
840 newEnergy = std::min(static_cast<uint8_t>(PEEP_MAX_ENERGY), newEnergy);
841
842 if (newEnergy != Energy)
843 {
844 Energy = newEnergy;
845 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_2;
846 }
847
848 uint8_t newHappiness = Happiness;
849 uint8_t newHappinessGrowth = HappinessTarget;
850 if (newHappiness >= newHappinessGrowth)
851 {
852 newHappiness = std::max(newHappiness - 4, 0);
853 if (newHappiness < newHappinessGrowth)
854 newHappiness = newHappinessGrowth;
855 }
856 else
857 {
858 newHappiness = std::min(255, newHappiness + 4);
859 if (newHappiness > newHappinessGrowth)
860 newHappiness = newHappinessGrowth;
861 }
862
863 if (newHappiness != Happiness)
864 {
865 Happiness = newHappiness;
866 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_2;
867 }
868
869 uint8_t newNausea = Nausea;
870 uint8_t newNauseaGrowth = NauseaTarget;
871 if (newNausea >= newNauseaGrowth)
872 {
873 newNausea = std::max(newNausea - 4, 0);
874 if (newNausea < newNauseaGrowth)
875 newNausea = newNauseaGrowth;
876 }
877 else
878 {
879 newNausea = std::min(255, newNausea + 4);
880 if (newNausea > newNauseaGrowth)
881 newNausea = newNauseaGrowth;
882 }
883
884 if (newNausea != Nausea)
885 {
886 Nausea = newNausea;
887 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_2;
888 }
889 }
890
Tick128UpdateGuest(int32_t index)891 void Guest::Tick128UpdateGuest(int32_t index)
892 {
893 if (static_cast<uint32_t>(index & 0x1FF) == (gCurrentTicks & 0x1FF))
894 {
895 /* Effect of masking with 0x1FF here vs mask 0x7F,
896 * which is the condition for calling this function, is
897 * to reduce how often the content in this conditional
898 * is executed to once every four calls. */
899 if (PeepFlags & PEEP_FLAGS_CROWDED)
900 {
901 PeepThoughtType thought_type = crowded_thoughts[scenario_rand() & 0xF];
902 if (thought_type != PeepThoughtType::None)
903 {
904 InsertNewThought(thought_type);
905 }
906 }
907
908 if (PeepFlags & PEEP_FLAGS_EXPLODE && x != LOCATION_NULL)
909 {
910 if (State == PeepState::Walking || State == PeepState::Sitting)
911 {
912 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::Crash, GetLocation());
913
914 ExplosionCloud::Create({ x, y, z + 16 });
915 ExplosionFlare::Create({ x, y, z + 16 });
916
917 Remove();
918 return;
919 }
920
921 PeepFlags &= ~PEEP_FLAGS_EXPLODE;
922 }
923
924 if (PeepFlags & PEEP_FLAGS_HUNGER)
925 {
926 if (Hunger >= 15)
927 Hunger -= 15;
928 }
929
930 if (PeepFlags & PEEP_FLAGS_TOILET)
931 {
932 if (Toilet <= 180)
933 Toilet += 50;
934 }
935
936 if (PeepFlags & PEEP_FLAGS_HAPPINESS)
937 {
938 HappinessTarget = 5;
939 }
940
941 if (PeepFlags & PEEP_FLAGS_NAUSEA)
942 {
943 NauseaTarget = 200;
944 if (Nausea <= 130)
945 Nausea = 130;
946 }
947
948 if (Angriness != 0)
949 Angriness--;
950
951 if (State == PeepState::Walking || State == PeepState::Sitting)
952 {
953 SurroundingsThoughtTimeout++;
954 if (SurroundingsThoughtTimeout >= 18)
955 {
956 SurroundingsThoughtTimeout = 0;
957 if (x != LOCATION_NULL)
958 {
959 PeepThoughtType thought_type = peep_assess_surroundings(x & 0xFFE0, y & 0xFFE0, z);
960
961 if (thought_type != PeepThoughtType::None)
962 {
963 InsertNewThought(thought_type);
964 HappinessTarget = std::min(PEEP_MAX_HAPPINESS, HappinessTarget + 45);
965 }
966 }
967 }
968 }
969
970 UpdateSpriteType();
971
972 if (State == PeepState::OnRide || State == PeepState::EnteringRide)
973 {
974 GuestTimeOnRide = std::min(255, GuestTimeOnRide + 1);
975
976 if (PeepFlags & PEEP_FLAGS_WOW)
977 {
978 InsertNewThought(PeepThoughtType::Wow2);
979 }
980
981 if (GuestTimeOnRide > 15)
982 {
983 HappinessTarget = std::max(0, HappinessTarget - 5);
984
985 if (GuestTimeOnRide > 22)
986 {
987 auto ride = get_ride(CurrentRide);
988 if (ride != nullptr)
989 {
990 PeepThoughtType thought_type = ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IN_RIDE)
991 ? PeepThoughtType::GetOut
992 : PeepThoughtType::GetOff;
993
994 InsertNewThought(thought_type, CurrentRide);
995 }
996 }
997 }
998 }
999
1000 if (State == PeepState::Walking && !OutsideOfPark && !(PeepFlags & PEEP_FLAGS_LEAVING_PARK) && GuestNumRides == 0
1001 && GuestHeadingToRideId == RIDE_ID_NULL)
1002 {
1003 uint32_t time_duration = gCurrentTicks - ParkEntryTime;
1004 time_duration /= 2048;
1005
1006 if (time_duration >= 5)
1007 {
1008 PickRideToGoOn();
1009
1010 if (GuestHeadingToRideId == RIDE_ID_NULL)
1011 {
1012 HappinessTarget = std::max(HappinessTarget - 128, 0);
1013 peep_leave_park(this);
1014 peep_update_hunger(this);
1015 loc_68F9F3();
1016 loc_68FA89();
1017 return;
1018 }
1019 }
1020 }
1021
1022 if ((scenario_rand() & 0xFFFF) <= ((HasItem(ShopItem::Map)) ? 8192U : 2184U))
1023 {
1024 PickRideToGoOn();
1025 }
1026
1027 if (static_cast<uint32_t>(index & 0x3FF) == (gCurrentTicks & 0x3FF))
1028 {
1029 /* Effect of masking with 0x3FF here vs mask 0x1FF,
1030 * which is used in the encompassing conditional, is
1031 * to reduce how often the content in this conditional
1032 * is executed to once every second time the encompassing
1033 * conditional executes. */
1034
1035 if (!OutsideOfPark && (State == PeepState::Walking || State == PeepState::Sitting))
1036 {
1037 uint8_t num_thoughts = 0;
1038 PeepThoughtType possible_thoughts[5];
1039
1040 if (PeepFlags & PEEP_FLAGS_LEAVING_PARK)
1041 {
1042 possible_thoughts[num_thoughts++] = PeepThoughtType::GoHome;
1043 }
1044 else
1045 {
1046 if (Energy <= 70 && Happiness < 128)
1047 {
1048 possible_thoughts[num_thoughts++] = PeepThoughtType::Tired;
1049 }
1050
1051 if (Hunger <= 10 && !HasFoodOrDrink())
1052 {
1053 possible_thoughts[num_thoughts++] = PeepThoughtType::Hungry;
1054 }
1055
1056 if (Thirst <= 25 && !HasFoodOrDrink())
1057 {
1058 possible_thoughts[num_thoughts++] = PeepThoughtType::Thirsty;
1059 }
1060
1061 if (Toilet >= 160)
1062 {
1063 possible_thoughts[num_thoughts++] = PeepThoughtType::Toilet;
1064 }
1065
1066 if (!(gParkFlags & PARK_FLAGS_NO_MONEY) && CashInPocket <= MONEY(9, 00) && Happiness >= 105 && Energy >= 70)
1067 {
1068 /* The energy check was originally a second check on happiness.
1069 * This was superfluous so should probably check something else.
1070 * Guessed that this should really be checking energy, since
1071 * the addresses for happiness and energy are quite close,
1072 * 70 is also the threshold for tired thoughts (see above) and
1073 * it makes sense that a tired peep might not think about getting
1074 * more money. */
1075 possible_thoughts[num_thoughts++] = PeepThoughtType::RunningOut;
1076 }
1077 }
1078
1079 if (num_thoughts != 0)
1080 {
1081 PeepThoughtType chosen_thought = possible_thoughts[scenario_rand() % num_thoughts];
1082
1083 InsertNewThought(chosen_thought);
1084
1085 switch (chosen_thought)
1086 {
1087 case PeepThoughtType::Hungry:
1088 peep_head_for_nearest_ride_with_flags(this, RIDE_TYPE_FLAG_SELLS_FOOD);
1089 break;
1090 case PeepThoughtType::Thirsty:
1091 peep_head_for_nearest_ride_with_flags(this, RIDE_TYPE_FLAG_SELLS_DRINKS);
1092 break;
1093 case PeepThoughtType::Toilet:
1094 peep_head_for_nearest_ride_with_flags(this, RIDE_TYPE_FLAG_IS_TOILET);
1095 break;
1096 case PeepThoughtType::RunningOut:
1097 peep_head_for_nearest_ride_type(this, RIDE_TYPE_CASH_MACHINE);
1098 break;
1099 default:
1100 break;
1101 }
1102 }
1103 }
1104 }
1105 else
1106 {
1107 /* This branch of the conditional is executed on the
1108 * remaining times the encompassing conditional is
1109 * executed (which is also every second time, but
1110 * the alternate time to the true branch). */
1111 if (Nausea >= 140)
1112 {
1113 PeepThoughtType thought_type = PeepThoughtType::Sick;
1114 if (Nausea >= 200)
1115 {
1116 thought_type = PeepThoughtType::VerySick;
1117 peep_head_for_nearest_ride_type(this, RIDE_TYPE_FIRST_AID);
1118 }
1119 InsertNewThought(thought_type);
1120 }
1121 }
1122
1123 switch (State)
1124 {
1125 case PeepState::Walking:
1126 case PeepState::LeavingPark:
1127 case PeepState::EnteringPark:
1128 peep_decide_whether_to_leave_park(this);
1129 peep_update_hunger(this);
1130 break;
1131
1132 case PeepState::Sitting:
1133 if (EnergyTarget <= 135)
1134 EnergyTarget += 5;
1135
1136 if (Thirst >= 5)
1137 {
1138 Thirst -= 4;
1139 Toilet = std::min(255, Toilet + 3);
1140 }
1141
1142 if (NauseaTarget >= 50)
1143 NauseaTarget -= 6;
1144
1145 // In the original this branched differently
1146 // but it would mean setting the peep happiness from
1147 // a thought type entry which i think is incorrect.
1148 peep_update_hunger(this);
1149 break;
1150
1151 case PeepState::Queuing:
1152 if (TimeInQueue >= 2000)
1153 {
1154 /* Peep happiness is affected once the peep has been waiting
1155 * too long in a queue. */
1156 bool found = false;
1157 for (auto* pathElement : TileElementsView<PathElement>(NextLoc))
1158 {
1159 if (pathElement->GetBaseZ() != NextLoc.z)
1160 continue;
1161
1162 // Check if the footpath has a queue line TV monitor on it
1163 if (pathElement->HasAddition() && !pathElement->AdditionIsGhost())
1164 {
1165 auto* pathAddEntry = pathElement->GetAdditionEntry();
1166 if (pathAddEntry != nullptr && (pathAddEntry->flags & PATH_BIT_FLAG_IS_QUEUE_SCREEN))
1167 {
1168 found = true;
1169 }
1170 }
1171 break;
1172 }
1173
1174 if (found)
1175 {
1176 /* Queue line TV monitors make the peeps waiting in the queue
1177 * slowly happier, up to a certain level. */
1178 /* Why don't queue line TV monitors start affecting the peeps
1179 * as soon as they join the queue?? */
1180 if (HappinessTarget < 90)
1181 HappinessTarget = 90;
1182
1183 if (HappinessTarget < 165)
1184 HappinessTarget += 2;
1185 }
1186 else
1187 {
1188 /* Without a queue line TV monitor peeps waiting too long
1189 * in a queue get less happy. */
1190 HappinessTarget = std::max(HappinessTarget - 4, 0);
1191 }
1192 }
1193 peep_update_hunger(this);
1194 break;
1195 case PeepState::EnteringRide:
1196 if (SubState == 17 || SubState == 15)
1197 {
1198 peep_decide_whether_to_leave_park(this);
1199 }
1200 peep_update_hunger(this);
1201 break;
1202 default:
1203 break;
1204 }
1205
1206 loc_68F9F3();
1207 }
1208
1209 loc_68FA89();
1210 }
1211
1212 /**
1213 *
1214 * rct2: 0x00691677
1215 */
TryGetUpFromSitting()1216 void Guest::TryGetUpFromSitting()
1217 {
1218 // Eats all food first
1219 if (HasFoodOrDrink())
1220 return;
1221
1222 TimeToSitdown--;
1223 if (TimeToSitdown)
1224 return;
1225
1226 SetState(PeepState::Walking);
1227
1228 // Set destination to the centre of the tile.
1229 auto destination = GetLocation().ToTileCentre();
1230 SetDestination(destination, 5);
1231 UpdateCurrentActionSpriteType();
1232 }
1233
1234 /**
1235 *
1236 * rct2: 0x0069152B
1237 */
UpdateSitting()1238 void Guest::UpdateSitting()
1239 {
1240 if (SittingSubState == PeepSittingSubState::TryingToSit)
1241 {
1242 if (!CheckForPath())
1243 return;
1244 // 691541
1245
1246 uint8_t pathingResult;
1247 PerformNextAction(pathingResult);
1248 if (!(pathingResult & PATHING_DESTINATION_REACHED))
1249 return;
1250
1251 auto loc = GetLocation().ToTileStart() + CoordsXYZ{ BenchUseOffsets[Var37 & 0x7], 0 };
1252
1253 MoveTo(loc);
1254
1255 sprite_direction = ((Var37 + 2) & 3) * 8;
1256 Action = PeepActionType::Idle;
1257 NextActionSpriteType = PeepActionSpriteType::SittingIdle;
1258 SwitchNextActionSpriteType();
1259
1260 SittingSubState = PeepSittingSubState::SatDown;
1261
1262 // Sets time to sit on seat
1263 TimeToSitdown = (129 - Energy) * 16 + 50;
1264 }
1265 else if (SittingSubState == PeepSittingSubState::SatDown)
1266 {
1267 if (!IsActionInterruptable())
1268 {
1269 UpdateAction();
1270 if (!IsActionWalking())
1271 return;
1272
1273 Action = PeepActionType::Idle;
1274 TryGetUpFromSitting();
1275 return;
1276 }
1277
1278 if ((PeepFlags & PEEP_FLAGS_LEAVING_PARK))
1279 {
1280 SetState(PeepState::Walking);
1281
1282 // Set destination to the centre of the tile
1283 auto destination = GetLocation().ToTileCentre();
1284 SetDestination(destination, 5);
1285 UpdateCurrentActionSpriteType();
1286 return;
1287 }
1288
1289 if (SpriteType == PeepSpriteType::Umbrella)
1290 {
1291 TryGetUpFromSitting();
1292 return;
1293 }
1294
1295 if (HasFoodOrDrink())
1296 {
1297 if ((scenario_rand() & 0xFFFF) > 1310)
1298 {
1299 TryGetUpFromSitting();
1300 return;
1301 }
1302 Action = PeepActionType::SittingEatFood;
1303 ActionFrame = 0;
1304 ActionSpriteImageOffset = 0;
1305 UpdateCurrentActionSpriteType();
1306 return;
1307 }
1308
1309 int32_t rand = scenario_rand();
1310 if ((rand & 0xFFFF) > 131)
1311 {
1312 TryGetUpFromSitting();
1313 return;
1314 }
1315 if (SpriteType == PeepSpriteType::Balloon || SpriteType == PeepSpriteType::Hat)
1316 {
1317 TryGetUpFromSitting();
1318 return;
1319 }
1320
1321 Action = PeepActionType::SittingLookAroundLeft;
1322 if (rand & 0x80000000)
1323 {
1324 Action = PeepActionType::SittingLookAroundRight;
1325 }
1326
1327 if (rand & 0x40000000)
1328 {
1329 Action = PeepActionType::SittingCheckWatch;
1330 }
1331 ActionFrame = 0;
1332 ActionSpriteImageOffset = 0;
1333 UpdateCurrentActionSpriteType();
1334 return;
1335 }
1336 }
1337
1338 /**
1339 * To simplify check of 0x36BA3E0 and 0x11FF78
1340 * returns false on no food.
1341 */
GetFoodOrDrinkFlags() const1342 int64_t Guest::GetFoodOrDrinkFlags() const
1343 {
1344 return GetItemFlags() & (ShopItemsGetAllFoods() | ShopItemsGetAllDrinks());
1345 }
1346
GetEmptyContainerFlags() const1347 int64_t Guest::GetEmptyContainerFlags() const
1348 {
1349 return GetItemFlags() & ShopItemsGetAllContainers();
1350 }
1351
HasFoodOrDrink() const1352 bool Guest::HasFoodOrDrink() const
1353 {
1354 return GetFoodOrDrinkFlags() != 0;
1355 }
1356
1357 /**
1358 * To simplify check of NOT(0x12BA3C0 and 0x118F48)
1359 * returns 0 on no food.
1360 */
HasDrink() const1361 bool Guest::HasDrink() const
1362 {
1363 return GetItemFlags() & ShopItemsGetAllDrinks();
1364 }
1365
HasEmptyContainer() const1366 bool Guest::HasEmptyContainer() const
1367 {
1368 return GetEmptyContainerFlags() != 0;
1369 }
1370
1371 /**
1372 *
1373 * rct2: 0x69C308
1374 * Check if lost.
1375 */
CheckIfLost()1376 void Guest::CheckIfLost()
1377 {
1378 if (!(PeepFlags & PEEP_FLAGS_LOST))
1379 {
1380 if (ride_get_count() < 2)
1381 return;
1382 PeepFlags ^= PEEP_FLAGS_21;
1383
1384 if (!(PeepFlags & PEEP_FLAGS_21))
1385 return;
1386
1387 TimeLost++;
1388 if (TimeLost != 254)
1389 return;
1390 TimeLost = 230;
1391 }
1392 InsertNewThought(PeepThoughtType::Lost);
1393
1394 HappinessTarget = std::max(HappinessTarget - 30, 0);
1395 }
1396
1397 /**
1398 *
1399 * rct2: 0x69C26B
1400 * Check if can't find ride.
1401 */
CheckCantFindRide()1402 void Guest::CheckCantFindRide()
1403 {
1404 if (GuestHeadingToRideId == RIDE_ID_NULL)
1405 return;
1406
1407 // Peeps will think "I can't find ride X" twice before giving up completely.
1408 if (GuestIsLostCountdown == 30 || GuestIsLostCountdown == 60)
1409 {
1410 InsertNewThought(PeepThoughtType::CantFind, GuestHeadingToRideId);
1411 HappinessTarget = std::max(HappinessTarget - 30, 0);
1412 }
1413
1414 GuestIsLostCountdown--;
1415 if (GuestIsLostCountdown != 0)
1416 return;
1417
1418 GuestHeadingToRideId = RIDE_ID_NULL;
1419 rct_window* w = window_find_by_number(WC_PEEP, sprite_index);
1420
1421 if (w != nullptr)
1422 {
1423 window_event_invalidate_call(w);
1424 }
1425
1426 window_invalidate_by_number(WC_PEEP, sprite_index);
1427 }
1428
1429 /**
1430 *
1431 * rct2: 0x69C2D0
1432 * Check if can't find exit.
1433 */
CheckCantFindExit()1434 void Guest::CheckCantFindExit()
1435 {
1436 if (!(PeepFlags & PEEP_FLAGS_LEAVING_PARK))
1437 return;
1438
1439 // Peeps who can't find the park exit will continue to get less happy until they find it.
1440 if (GuestIsLostCountdown == 1)
1441 {
1442 InsertNewThought(PeepThoughtType::CantFindExit);
1443 HappinessTarget = std::max(HappinessTarget - 30, 0);
1444 }
1445
1446 if (--GuestIsLostCountdown == 0)
1447 GuestIsLostCountdown = 90;
1448 }
1449
1450 /** Main logic to decide whether a peep should buy an item in question
1451 *
1452 * Also handles the purchase as well, so once it returns, the peep will have the
1453 * item and the money will have been deducted.
1454 *
1455 * eax: shopItem | (rideIndex << 8)
1456 * ecx: price
1457 * esi: *peep
1458 *
1459 * Returns 0 or 1 depending on if the peep decided to buy the item
1460 *
1461 * rct2: 0x0069AF1E
1462 */
DecideAndBuyItem(Ride * ride,ShopItem shopItem,money32 price)1463 bool Guest::DecideAndBuyItem(Ride* ride, ShopItem shopItem, money32 price)
1464 {
1465 money32 itemValue;
1466
1467 bool hasVoucher = false;
1468
1469 bool isRainingAndUmbrella = shopItem == ShopItem::Umbrella && climate_is_raining();
1470
1471 if ((HasItem(ShopItem::Voucher)) && (VoucherType == VOUCHER_TYPE_FOOD_OR_DRINK_FREE) && (VoucherShopItem == shopItem))
1472 {
1473 hasVoucher = true;
1474 }
1475
1476 if (HasItem(shopItem))
1477 {
1478 InsertNewThought(PeepThoughtType::AlreadyGot, EnumValue(shopItem));
1479 return false;
1480 }
1481
1482 if (GetShopItemDescriptor(shopItem).IsFoodOrDrink())
1483 {
1484 int32_t food = bitscanforward(GetFoodOrDrinkFlags());
1485 if (food != -1)
1486 {
1487 InsertNewThought(PeepThoughtType::HaventFinished, food);
1488 return false;
1489 }
1490
1491 if (Nausea >= 145)
1492 return false;
1493 }
1494
1495 if ((shopItem == ShopItem::Balloon || shopItem == ShopItem::IceCream || shopItem == ShopItem::Candyfloss
1496 || shopItem == ShopItem::Sunglasses)
1497 && climate_is_raining())
1498 {
1499 return false;
1500 }
1501
1502 if ((shopItem == ShopItem::Sunglasses || shopItem == ShopItem::IceCream) && gClimateCurrent.Temperature < 12)
1503 {
1504 return false;
1505 }
1506
1507 if (GetShopItemDescriptor(shopItem).IsFood() && (Hunger > 75))
1508 {
1509 InsertNewThought(PeepThoughtType::NotHungry);
1510 return false;
1511 }
1512
1513 if (GetShopItemDescriptor(shopItem).IsDrink() && (Thirst > 75))
1514 {
1515 InsertNewThought(PeepThoughtType::NotThirsty);
1516 return false;
1517 }
1518
1519 if (!isRainingAndUmbrella && (shopItem != ShopItem::Map) && GetShopItemDescriptor(shopItem).IsSouvenir() && !hasVoucher)
1520 {
1521 if (((scenario_rand() & 0x7F) + 0x73) > Happiness || GuestNumRides < 3)
1522 return false;
1523 }
1524
1525 if (!hasVoucher)
1526 {
1527 if (price != 0 && !(gParkFlags & PARK_FLAGS_NO_MONEY))
1528 {
1529 if (CashInPocket == 0)
1530 {
1531 InsertNewThought(PeepThoughtType::SpentMoney);
1532 return false;
1533 }
1534 if (price > CashInPocket)
1535 {
1536 InsertNewThought(PeepThoughtType::CantAffordItem, EnumValue(shopItem));
1537 return false;
1538 }
1539 }
1540
1541 if (gClimateCurrent.Temperature >= 21)
1542 itemValue = GetShopItemDescriptor(shopItem).HotValue;
1543 else if (gClimateCurrent.Temperature <= 11)
1544 itemValue = GetShopItemDescriptor(shopItem).ColdValue;
1545 else
1546 itemValue = GetShopItemDescriptor(shopItem).BaseValue;
1547
1548 if (itemValue < price)
1549 {
1550 itemValue -= price;
1551
1552 if (!isRainingAndUmbrella)
1553 {
1554 itemValue = -itemValue;
1555 if (Happiness >= 128)
1556 {
1557 itemValue /= 2;
1558 if (Happiness >= 180)
1559 itemValue /= 2;
1560 }
1561 if (itemValue > (static_cast<money16>(scenario_rand() & 0x07)))
1562 {
1563 // "I'm not paying that much for x"
1564 InsertNewThought(GetShopItemDescriptor(shopItem).TooMuchThought, ride->id);
1565 return false;
1566 }
1567 }
1568 }
1569 else
1570 {
1571 itemValue -= price;
1572 itemValue = std::max(8, itemValue);
1573
1574 if (!(gParkFlags & PARK_FLAGS_NO_MONEY))
1575 {
1576 if (itemValue >= static_cast<money32>(scenario_rand() & 0x07))
1577 {
1578 // "This x is a really good value"
1579 InsertNewThought(GetShopItemDescriptor(shopItem).GoodValueThought, ride->id);
1580 }
1581 }
1582
1583 int32_t happinessGrowth = itemValue * 4;
1584 HappinessTarget = std::min((HappinessTarget + happinessGrowth), PEEP_MAX_HAPPINESS);
1585 Happiness = std::min((Happiness + happinessGrowth), PEEP_MAX_HAPPINESS);
1586 }
1587
1588 // reset itemValue for satisfaction calculation
1589 if (gClimateCurrent.Temperature >= 21)
1590 itemValue = GetShopItemDescriptor(shopItem).HotValue;
1591 else if (gClimateCurrent.Temperature <= 11)
1592 itemValue = GetShopItemDescriptor(shopItem).ColdValue;
1593 else
1594 itemValue = GetShopItemDescriptor(shopItem).BaseValue;
1595 itemValue -= price;
1596 uint8_t satisfaction = 0;
1597 if (itemValue > -8)
1598 {
1599 satisfaction++;
1600 if (itemValue > -3)
1601 {
1602 satisfaction++;
1603 if (itemValue > 3)
1604 satisfaction++;
1605 }
1606 }
1607 ride_update_satisfaction(ride, satisfaction);
1608 }
1609
1610 // The peep has now decided to buy the item (or, specifically, has not been
1611 // dissuaded so far).
1612 GiveItem(shopItem);
1613
1614 if (shopItem == ShopItem::TShirt)
1615 TshirtColour = ride->track_colour[0].main;
1616
1617 if (shopItem == ShopItem::Hat)
1618 HatColour = ride->track_colour[0].main;
1619
1620 if (shopItem == ShopItem::Balloon)
1621 BalloonColour = ride->track_colour[0].main;
1622
1623 if (shopItem == ShopItem::Umbrella)
1624 UmbrellaColour = ride->track_colour[0].main;
1625
1626 if (shopItem == ShopItem::Map)
1627 ResetPathfindGoal();
1628
1629 uint16_t consumptionTime = GetShopItemDescriptor(shopItem).ConsumptionTime;
1630 TimeToConsume = std::min((TimeToConsume + consumptionTime), 255);
1631
1632 if (shopItem == ShopItem::Photo)
1633 Photo1RideRef = ride->id;
1634
1635 if (shopItem == ShopItem::Photo2)
1636 Photo2RideRef = ride->id;
1637
1638 if (shopItem == ShopItem::Photo3)
1639 Photo3RideRef = ride->id;
1640
1641 if (shopItem == ShopItem::Photo4)
1642 Photo4RideRef = ride->id;
1643
1644 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_INVENTORY;
1645 UpdateSpriteType();
1646 if (PeepFlags & PEEP_FLAGS_TRACKING)
1647 {
1648 auto ft = Formatter();
1649 FormatNameTo(ft);
1650 ft.Add<rct_string_id>(GetShopItemDescriptor(shopItem).Naming.Indefinite);
1651 if (gConfigNotifications.guest_bought_item)
1652 {
1653 News::AddItemToQueue(News::ItemType::PeepOnRide, STR_PEEP_TRACKING_NOTIFICATION_BOUGHT_X, sprite_index, ft);
1654 }
1655 }
1656
1657 if (GetShopItemDescriptor(shopItem).IsFood())
1658 AmountOfFood++;
1659
1660 if (GetShopItemDescriptor(shopItem).IsDrink())
1661 AmountOfDrinks++;
1662
1663 if (GetShopItemDescriptor(shopItem).IsSouvenir())
1664 AmountOfSouvenirs++;
1665
1666 money16* expend_type = &PaidOnSouvenirs;
1667 ExpenditureType expenditure = ExpenditureType::ShopStock;
1668
1669 if (GetShopItemDescriptor(shopItem).IsFood())
1670 {
1671 expend_type = &PaidOnFood;
1672 expenditure = ExpenditureType::FoodDrinkStock;
1673 }
1674
1675 if (GetShopItemDescriptor(shopItem).IsDrink())
1676 {
1677 expend_type = &PaidOnDrink;
1678 expenditure = ExpenditureType::FoodDrinkStock;
1679 }
1680
1681 if (!(gParkFlags & PARK_FLAGS_NO_MONEY))
1682 finance_payment(GetShopItemDescriptor(shopItem).Cost, expenditure);
1683
1684 // Sets the expenditure type to *_FOODDRINK_SALES or *_SHOP_SALES appropriately.
1685 expenditure = static_cast<ExpenditureType>(static_cast<int32_t>(expenditure) - 1);
1686 if (hasVoucher)
1687 {
1688 RemoveItem(ShopItem::Voucher);
1689 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_INVENTORY;
1690 }
1691 else if (!(gParkFlags & PARK_FLAGS_NO_MONEY))
1692 {
1693 SpendMoney(*expend_type, price, expenditure);
1694 }
1695 ride->total_profit += (price - GetShopItemDescriptor(shopItem).Cost);
1696 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_INCOME;
1697 ride->cur_num_customers++;
1698 ride->total_customers++;
1699 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;
1700
1701 return true;
1702 }
1703
1704 /**
1705 * Updates various peep stats upon entering a ride, as well as updating the
1706 * ride's satisfaction value.
1707 * rct2: 0x0069545B
1708 */
OnEnterRide(Ride * ride)1709 void Guest::OnEnterRide(Ride* ride)
1710 {
1711 if (ride == nullptr)
1712 return;
1713
1714 // Calculate how satisfying the ride is for the peep. Can range from -140 to +105.
1715 int16_t satisfaction = peep_calculate_ride_satisfaction(this, ride);
1716
1717 // Update the satisfaction stat of the ride.
1718 uint8_t rideSatisfaction = 0;
1719 if (satisfaction >= 40)
1720 rideSatisfaction = 3;
1721 else if (satisfaction >= 20)
1722 rideSatisfaction = 2;
1723 else if (satisfaction >= 0)
1724 rideSatisfaction = 1;
1725
1726 ride_update_satisfaction(ride, rideSatisfaction);
1727
1728 // Update various peep stats.
1729 if (GuestNumRides < 255)
1730 GuestNumRides++;
1731
1732 SetHasRidden(ride);
1733 peep_update_favourite_ride(this, ride);
1734 HappinessTarget = std::clamp(HappinessTarget + satisfaction, 0, PEEP_MAX_HAPPINESS);
1735 peep_update_ride_nausea_growth(this, ride);
1736 }
1737
1738 /**
1739 *
1740 * rct2: 0x0069576E
1741 */
OnExitRide(Ride * ride)1742 void Guest::OnExitRide(Ride* ride)
1743 {
1744 if (PeepFlags & PEEP_FLAGS_RIDE_SHOULD_BE_MARKED_AS_FAVOURITE)
1745 {
1746 PeepFlags &= ~PEEP_FLAGS_RIDE_SHOULD_BE_MARKED_AS_FAVOURITE;
1747 FavouriteRide = ride->id;
1748 // TODO fix this flag name or add another one
1749 WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
1750 }
1751 Happiness = HappinessTarget;
1752 Nausea = NauseaTarget;
1753 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_STATS;
1754
1755 if (PeepFlags & PEEP_FLAGS_LEAVING_PARK)
1756 PeepFlags &= ~(PEEP_FLAGS_PARK_ENTRANCE_CHOSEN);
1757
1758 if (ride != nullptr && peep_should_go_on_ride_again(this, ride))
1759 {
1760 GuestHeadingToRideId = ride->id;
1761 GuestIsLostCountdown = 200;
1762 ResetPathfindGoal();
1763 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_ACTION;
1764 }
1765
1766 if (peep_should_preferred_intensity_increase(this))
1767 {
1768 if (Intensity.GetMaximum() < 15)
1769 {
1770 Intensity = Intensity.WithMaximum(Intensity.GetMaximum() + 1);
1771 }
1772 }
1773
1774 if (ride != nullptr && peep_really_liked_ride(this, ride))
1775 {
1776 InsertNewThought(PeepThoughtType::WasGreat, ride->id);
1777
1778 static constexpr OpenRCT2::Audio::SoundId laughs[3] = {
1779 OpenRCT2::Audio::SoundId::Laugh1,
1780 OpenRCT2::Audio::SoundId::Laugh2,
1781 OpenRCT2::Audio::SoundId::Laugh3,
1782 };
1783 int32_t laughType = scenario_rand() & 7;
1784 if (laughType < 3)
1785 {
1786 OpenRCT2::Audio::Play3D(laughs[laughType], GetLocation());
1787 }
1788 }
1789
1790 if (ride != nullptr)
1791 {
1792 ride->total_customers++;
1793 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;
1794 }
1795 }
1796
1797 /**
1798 *
1799 * rct2: 0x00695DD2
1800 */
PickRideToGoOn()1801 void Guest::PickRideToGoOn()
1802 {
1803 if (State != PeepState::Walking)
1804 return;
1805 if (GuestHeadingToRideId != RIDE_ID_NULL)
1806 return;
1807 if (PeepFlags & PEEP_FLAGS_LEAVING_PARK)
1808 return;
1809 if (HasFoodOrDrink())
1810 return;
1811 if (x == LOCATION_NULL)
1812 return;
1813
1814 auto ride = FindBestRideToGoOn();
1815 if (ride != nullptr)
1816 {
1817 // Head to that ride
1818 GuestHeadingToRideId = ride->id;
1819 GuestIsLostCountdown = 200;
1820 ResetPathfindGoal();
1821 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_ACTION;
1822
1823 // Make peep look at their map if they have one
1824 if (HasItem(ShopItem::Map))
1825 {
1826 ReadMap();
1827 }
1828 }
1829 }
1830
FindBestRideToGoOn()1831 Ride* Guest::FindBestRideToGoOn()
1832 {
1833 // Pick the most exciting ride
1834 auto rideConsideration = FindRidesToGoOn();
1835 Ride* mostExcitingRide = nullptr;
1836 for (auto& ride : GetRideManager())
1837 {
1838 const auto rideIndex = EnumValue(ride.id);
1839 if (rideConsideration.size() > rideIndex && rideConsideration[rideIndex])
1840 {
1841 if (!(ride.lifecycle_flags & RIDE_LIFECYCLE_QUEUE_FULL))
1842 {
1843 if (ShouldGoOnRide(&ride, 0, false, true) && ride_has_ratings(&ride))
1844 {
1845 if (mostExcitingRide == nullptr || ride.excitement > mostExcitingRide->excitement)
1846 {
1847 mostExcitingRide = &ride;
1848 }
1849 }
1850 }
1851 }
1852 }
1853 return mostExcitingRide;
1854 }
1855
FindRidesToGoOn()1856 std::bitset<MAX_RIDES> Guest::FindRidesToGoOn()
1857 {
1858 std::bitset<MAX_RIDES> rideConsideration;
1859
1860 // FIX Originally checked for a toy, likely a mistake and should be a map,
1861 // but then again this seems to only allow the peep to go on
1862 // rides they haven't been on before.
1863 if (HasItem(ShopItem::Map))
1864 {
1865 // Consider rides that peep hasn't been on yet
1866 for (auto& ride : GetRideManager())
1867 {
1868 if (!HasRidden(&ride))
1869 {
1870 rideConsideration[EnumValue(ride.id)] = true;
1871 }
1872 }
1873 }
1874 else
1875 {
1876 // Take nearby rides into consideration
1877 constexpr auto radius = 10 * 32;
1878 int32_t cx = floor2(x, 32);
1879 int32_t cy = floor2(y, 32);
1880 for (int32_t tileX = cx - radius; tileX <= cx + radius; tileX += COORDS_XY_STEP)
1881 {
1882 for (int32_t tileY = cy - radius; tileY <= cy + radius; tileY += COORDS_XY_STEP)
1883 {
1884 auto location = CoordsXY{ tileX, tileY };
1885 if (!map_is_location_valid(location))
1886 continue;
1887
1888 for (auto* trackElement : TileElementsView<TrackElement>(location))
1889 {
1890 auto rideIndex = trackElement->GetRideIndex();
1891 if (rideIndex != RIDE_ID_NULL)
1892 {
1893 rideConsideration[EnumValue(rideIndex)] = true;
1894 }
1895 }
1896 }
1897 }
1898
1899 // Always take the tall rides into consideration (realistic as you can usually see them from anywhere in the park)
1900 for (auto& ride : GetRideManager())
1901 {
1902 if (ride.highest_drop_height > 66 || ride.excitement >= RIDE_RATING(8, 00))
1903 {
1904 rideConsideration[EnumValue(ride.id)] = true;
1905 }
1906 }
1907 }
1908
1909 return rideConsideration;
1910 }
1911
1912 /**
1913 * This function is called whenever a peep is deciding whether or not they want
1914 * to go on a ride or visit a shop. They may be physically present at the
1915 * ride/shop, or they may just be thinking about it.
1916 * rct2: 0x006960AB
1917 */
ShouldGoOnRide(Ride * ride,int32_t entranceNum,bool atQueue,bool thinking)1918 bool Guest::ShouldGoOnRide(Ride* ride, int32_t entranceNum, bool atQueue, bool thinking)
1919 {
1920 // Indicates whether a peep is physically at the ride, or is just thinking about going on the ride.
1921 bool peepAtRide = !thinking;
1922
1923 if (ride->status == RideStatus::Open && !(ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN))
1924 {
1925 // Peeps that are leaving the park will refuse to go on any rides, with the exception of free transport rides.
1926 assert(ride->type < std::size(RideTypeDescriptors));
1927 if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_TRANSPORT_RIDE) || ride->value == RIDE_VALUE_UNDEFINED
1928 || ride_get_price(ride) != 0)
1929 {
1930 if (PeepFlags & PEEP_FLAGS_LEAVING_PARK)
1931 {
1932 ChoseNotToGoOnRide(ride, peepAtRide, false);
1933 return false;
1934 }
1935 }
1936
1937 if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
1938 {
1939 return ShouldGoToShop(ride, peepAtRide);
1940 }
1941
1942 // This used to check !(flags & 2), but the function is only ever called with flags = 0, 1 or 6.
1943 // This means we can use the existing !(flags & 4) check.
1944 if (peepAtRide)
1945 {
1946 // Peeps won't join a queue that has 1000 peeps already in it.
1947 if (ride->stations[entranceNum].QueueLength >= 1000)
1948 {
1949 peep_tried_to_enter_full_queue(this, ride);
1950 return false;
1951 }
1952
1953 // Rides without queues can only have one peep waiting at a time.
1954 if (!atQueue)
1955 {
1956 if (ride->stations[entranceNum].LastPeepInQueue != SPRITE_INDEX_NULL)
1957 {
1958 peep_tried_to_enter_full_queue(this, ride);
1959 return false;
1960 }
1961 }
1962 else
1963 {
1964 // Check if there's room in the queue for the peep to enter.
1965 Guest* lastPeepInQueue = GetEntity<Guest>(ride->stations[entranceNum].LastPeepInQueue);
1966 if (lastPeepInQueue != nullptr && (abs(lastPeepInQueue->z - z) <= 6))
1967 {
1968 int32_t dx = abs(lastPeepInQueue->x - x);
1969 int32_t dy = abs(lastPeepInQueue->y - y);
1970 int32_t maxD = std::max(dx, dy);
1971
1972 // Unlike normal paths, peeps cannot overlap when queueing for a ride.
1973 // This check enforces a minimum distance between peeps entering the queue.
1974 if (maxD < 8)
1975 {
1976 peep_tried_to_enter_full_queue(this, ride);
1977 return false;
1978 }
1979
1980 // This checks if there's a peep standing still at the very end of the queue.
1981 if (maxD <= 13 && lastPeepInQueue->TimeInQueue > 10)
1982 {
1983 peep_tried_to_enter_full_queue(this, ride);
1984 return false;
1985 }
1986 }
1987 }
1988 }
1989
1990 // Assuming the queue conditions are met, peeps will always go on free transport rides.
1991 // Ride ratings, recent crashes and weather will all be ignored.
1992 money16 ridePrice = ride_get_price(ride);
1993 if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_TRANSPORT_RIDE) || ride->value == RIDE_VALUE_UNDEFINED
1994 || ridePrice != 0)
1995 {
1996 if (PreviousRide == ride->id)
1997 {
1998 ChoseNotToGoOnRide(ride, peepAtRide, false);
1999 return false;
2000 }
2001
2002 // Basic price checks
2003 if (ridePrice != 0 && !peep_has_voucher_for_free_ride(this, ride) && !(gParkFlags & PARK_FLAGS_NO_MONEY))
2004 {
2005 if (ridePrice > CashInPocket)
2006 {
2007 if (peepAtRide)
2008 {
2009 if (CashInPocket <= 0)
2010 {
2011 InsertNewThought(PeepThoughtType::SpentMoney);
2012 }
2013 else
2014 {
2015 InsertNewThought(PeepThoughtType::CantAffordRide, ride->id);
2016 }
2017 }
2018 ChoseNotToGoOnRide(ride, peepAtRide, true);
2019 return false;
2020 }
2021 }
2022
2023 // If happy enough, peeps will ignore the fact that a ride has recently crashed.
2024 if (ride->last_crash_type != RIDE_CRASH_TYPE_NONE && Happiness < 225)
2025 {
2026 if (peepAtRide)
2027 {
2028 InsertNewThought(PeepThoughtType::NotSafe, ride->id);
2029 if (HappinessTarget >= 64)
2030 {
2031 HappinessTarget -= 8;
2032 }
2033 ride_update_popularity(ride, 0);
2034 }
2035 ChoseNotToGoOnRide(ride, peepAtRide, true);
2036 return false;
2037 }
2038
2039 if (ride_has_ratings(ride))
2040 {
2041 // If a peep has already decided that they're going to go on a ride, they'll skip the weather and
2042 // excitement check and will only do a basic intensity check when they arrive at the ride itself.
2043 if (ride->id == GuestHeadingToRideId)
2044 {
2045 if (ride->intensity > RIDE_RATING(10, 00) && !gCheatsIgnoreRideIntensity)
2046 {
2047 peep_ride_is_too_intense(this, ride, peepAtRide);
2048 return false;
2049 }
2050 }
2051
2052 // Peeps won't go on rides that aren't sufficiently undercover while it's raining.
2053 // The threshold is fairly low and only requires about 10-15% of the ride to be undercover.
2054 if (climate_is_raining() && (ride->sheltered_eighths) < 3)
2055 {
2056 if (peepAtRide)
2057 {
2058 InsertNewThought(PeepThoughtType::NotWhileRaining, ride->id);
2059 if (HappinessTarget >= 64)
2060 {
2061 HappinessTarget -= 8;
2062 }
2063 ride_update_popularity(ride, 0);
2064 }
2065 ChoseNotToGoOnRide(ride, peepAtRide, true);
2066 return false;
2067 }
2068
2069 if (!gCheatsIgnoreRideIntensity)
2070 {
2071 // Intensity calculations. Even though the max intensity can go up to 15, it's capped
2072 // at 10.0 (before happiness calculations). A full happiness bar will increase the max
2073 // intensity and decrease the min intensity by about 2.5.
2074 ride_rating maxIntensity = std::min(Intensity.GetMaximum() * 100, 1000) + Happiness;
2075 ride_rating minIntensity = (Intensity.GetMinimum() * 100) - Happiness;
2076 if (ride->intensity < minIntensity)
2077 {
2078 if (peepAtRide)
2079 {
2080 InsertNewThought(PeepThoughtType::MoreThrilling, ride->id);
2081 if (HappinessTarget >= 64)
2082 {
2083 HappinessTarget -= 8;
2084 }
2085 ride_update_popularity(ride, 0);
2086 }
2087 ChoseNotToGoOnRide(ride, peepAtRide, true);
2088 return false;
2089 }
2090 if (ride->intensity > maxIntensity)
2091 {
2092 peep_ride_is_too_intense(this, ride, peepAtRide);
2093 return false;
2094 }
2095
2096 // Nausea calculations.
2097 ride_rating maxNausea = NauseaMaximumThresholds[(EnumValue(NauseaTolerance) & 3)] + Happiness;
2098
2099 if (ride->nausea > maxNausea)
2100 {
2101 if (peepAtRide)
2102 {
2103 InsertNewThought(PeepThoughtType::Sickening, ride->id);
2104 if (HappinessTarget >= 64)
2105 {
2106 HappinessTarget -= 8;
2107 }
2108 ride_update_popularity(ride, 0);
2109 }
2110 ChoseNotToGoOnRide(ride, peepAtRide, true);
2111 return false;
2112 }
2113
2114 // Very nauseous peeps will only go on very gentle rides.
2115 if (ride->nausea >= FIXED_2DP(1, 40) && Nausea > 160)
2116 {
2117 ChoseNotToGoOnRide(ride, peepAtRide, false);
2118 return false;
2119 }
2120 }
2121 }
2122
2123 // If the ride has not yet been rated and is capable of having g-forces,
2124 // there's a 90% chance that the peep will ignore it.
2125 if (!ride_has_ratings(ride) && ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_PEEP_CHECK_GFORCES))
2126 {
2127 if ((scenario_rand() & 0xFFFF) > 0x1999U)
2128 {
2129 ChoseNotToGoOnRide(ride, peepAtRide, false);
2130 return false;
2131 }
2132
2133 if (!gCheatsIgnoreRideIntensity)
2134 {
2135 if (ride->max_positive_vertical_g > FIXED_2DP(5, 00) || ride->max_negative_vertical_g < FIXED_2DP(-4, 00)
2136 || ride->max_lateral_g > FIXED_2DP(4, 00))
2137 {
2138 ChoseNotToGoOnRide(ride, peepAtRide, false);
2139 return false;
2140 }
2141 }
2142 }
2143
2144 uint32_t value = ride->value;
2145
2146 // If the value of the ride hasn't yet been calculated, peeps will be willing to pay any amount for the ride.
2147 if (value != 0xFFFF && !peep_has_voucher_for_free_ride(this, ride) && !(gParkFlags & PARK_FLAGS_NO_MONEY))
2148 {
2149 // The amount peeps are willing to pay is decreased by 75% if they had to pay to enter the park.
2150 if (PeepFlags & PEEP_FLAGS_HAS_PAID_FOR_PARK_ENTRY)
2151 value /= 4;
2152
2153 // Peeps won't pay more than twice the value of the ride.
2154 ridePrice = ride_get_price(ride);
2155 if (ridePrice > static_cast<money16>(value * 2))
2156 {
2157 if (peepAtRide)
2158 {
2159 InsertNewThought(PeepThoughtType::BadValue, ride->id);
2160 if (HappinessTarget >= 60)
2161 {
2162 HappinessTarget -= 16;
2163 }
2164 ride_update_popularity(ride, 0);
2165 }
2166 ChoseNotToGoOnRide(ride, peepAtRide, true);
2167 return false;
2168 }
2169
2170 // A ride is good value if the price is 50% or less of the ride value and the peep didn't pay to enter the park.
2171 if (ridePrice <= static_cast<money16>(value / 2) && peepAtRide)
2172 {
2173 if (!(gParkFlags & PARK_FLAGS_NO_MONEY))
2174 {
2175 if (!(PeepFlags & PEEP_FLAGS_HAS_PAID_FOR_PARK_ENTRY))
2176 {
2177 InsertNewThought(PeepThoughtType::GoodValue, ride->id);
2178 }
2179 }
2180 }
2181 }
2182 }
2183
2184 // At this point, the peep has decided to go on the ride.
2185 if (peepAtRide)
2186 {
2187 ride_update_popularity(ride, 1);
2188 }
2189
2190 if (ride->id == GuestHeadingToRideId)
2191 {
2192 peep_reset_ride_heading(this);
2193 }
2194
2195 ride->lifecycle_flags &= ~RIDE_LIFECYCLE_QUEUE_FULL;
2196 return true;
2197 }
2198
2199 ChoseNotToGoOnRide(ride, peepAtRide, false);
2200 return false;
2201 }
2202
ShouldGoToShop(Ride * ride,bool peepAtShop)2203 bool Guest::ShouldGoToShop(Ride* ride, bool peepAtShop)
2204 {
2205 // Peeps won't go to the same shop twice in a row.
2206 if (ride->id == PreviousRide)
2207 {
2208 ChoseNotToGoOnRide(ride, peepAtShop, true);
2209 return false;
2210 }
2211
2212 if (ride->type == RIDE_TYPE_TOILETS)
2213 {
2214 if (Toilet < 70)
2215 {
2216 ChoseNotToGoOnRide(ride, peepAtShop, true);
2217 return false;
2218 }
2219
2220 // The amount that peeps are willing to pay to use the Toilets scales with their toilet stat.
2221 // It effectively has a minimum of $0.10 (due to the check above) and a maximum of $0.60.
2222 if (ride_get_price(ride) * 40 > Toilet)
2223 {
2224 if (peepAtShop)
2225 {
2226 InsertNewThought(PeepThoughtType::NotPaying, ride->id);
2227 if (HappinessTarget >= 60)
2228 {
2229 HappinessTarget -= 16;
2230 }
2231 ride_update_popularity(ride, 0);
2232 }
2233 ChoseNotToGoOnRide(ride, peepAtShop, true);
2234 return false;
2235 }
2236 }
2237
2238 if (ride->type == RIDE_TYPE_FIRST_AID)
2239 {
2240 if (Nausea < 128)
2241 {
2242 ChoseNotToGoOnRide(ride, peepAtShop, true);
2243 return false;
2244 }
2245 }
2246
2247 // Basic price checks
2248 auto ridePrice = ride_get_price(ride);
2249 if (ridePrice != 0 && ridePrice > CashInPocket)
2250 {
2251 if (peepAtShop)
2252 {
2253 if (CashInPocket <= 0)
2254 {
2255 InsertNewThought(PeepThoughtType::SpentMoney);
2256 }
2257 else
2258 {
2259 InsertNewThought(PeepThoughtType::CantAffordRide, ride->id);
2260 }
2261 }
2262 ChoseNotToGoOnRide(ride, peepAtShop, true);
2263 return false;
2264 }
2265
2266 if (peepAtShop)
2267 {
2268 ride_update_popularity(ride, 1);
2269 if (ride->id == GuestHeadingToRideId)
2270 {
2271 peep_reset_ride_heading(this);
2272 }
2273 }
2274 return true;
2275 }
2276
2277 // Used when no logging to an expend type required
SpendMoney(money32 amount,ExpenditureType expenditure)2278 void Guest::SpendMoney(money32 amount, ExpenditureType expenditure)
2279 {
2280 money16 unused;
2281 SpendMoney(unused, amount, expenditure);
2282 }
2283
2284 /**
2285 *
2286 * rct2: 0x0069926C
2287 * Expend type was previously an offset saved in 0x00F1AEC0
2288 */
SpendMoney(money16 & peep_expend_type,money32 amount,ExpenditureType expenditure)2289 void Guest::SpendMoney(money16& peep_expend_type, money32 amount, ExpenditureType expenditure)
2290 {
2291 assert(!(gParkFlags & PARK_FLAGS_NO_MONEY));
2292
2293 CashInPocket = std::max(0, CashInPocket - amount);
2294 CashSpent += amount;
2295
2296 peep_expend_type += static_cast<money16>(amount);
2297
2298 window_invalidate_by_number(WC_PEEP, sprite_index);
2299
2300 finance_payment(-amount, expenditure);
2301
2302 if (gConfigGeneral.show_guest_purchases && !(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO))
2303 {
2304 // HACK Currently disabled for multiplayer due to limitation of all sprites
2305 // needing to be synchronised
2306 if (network_get_mode() == NETWORK_MODE_NONE && !gOpenRCT2Headless)
2307 {
2308 MoneyEffect::CreateAt(amount, GetLocation(), true);
2309 }
2310 }
2311
2312 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::Purchase, GetLocation());
2313 }
2314
SetHasRidden(const Ride * ride)2315 void Guest::SetHasRidden(const Ride* ride)
2316 {
2317 OpenRCT2::RideUse::GetHistory().Add(sprite_index, ride->id);
2318
2319 SetHasRiddenRideType(ride->type);
2320 }
2321
HasRidden(const Ride * ride) const2322 bool Guest::HasRidden(const Ride* ride) const
2323 {
2324 return OpenRCT2::RideUse::GetHistory().Contains(sprite_index, ride->id);
2325 }
2326
SetHasRiddenRideType(int32_t rideType)2327 void Guest::SetHasRiddenRideType(int32_t rideType)
2328 {
2329 // This is needed to avoid desyncs. TODO: remove once the new save format is introduced.
2330 rideType = OpenRCT2RideTypeToRCT2RideType(rideType);
2331
2332 OpenRCT2::RideUse::GetTypeHistory().Add(sprite_index, rideType);
2333 }
2334
HasRiddenRideType(int32_t rideType) const2335 bool Guest::HasRiddenRideType(int32_t rideType) const
2336 {
2337 // This is needed to avoid desyncs. TODO: remove once the new save format is introduced.
2338 rideType = OpenRCT2RideTypeToRCT2RideType(rideType);
2339
2340 return OpenRCT2::RideUse::GetTypeHistory().Contains(sprite_index, rideType);
2341 }
2342
SetParkEntryTime(int32_t entryTime)2343 void Guest::SetParkEntryTime(int32_t entryTime)
2344 {
2345 ParkEntryTime = entryTime;
2346 }
2347
GetParkEntryTime() const2348 int32_t Guest::GetParkEntryTime() const
2349 {
2350 return ParkEntryTime;
2351 }
2352
ChoseNotToGoOnRide(Ride * ride,bool peepAtRide,bool updateLastRide)2353 void Guest::ChoseNotToGoOnRide(Ride* ride, bool peepAtRide, bool updateLastRide)
2354 {
2355 if (peepAtRide && updateLastRide)
2356 {
2357 PreviousRide = ride->id;
2358 PreviousRideTimeOut = 0;
2359 }
2360
2361 if (ride->id == GuestHeadingToRideId)
2362 {
2363 peep_reset_ride_heading(this);
2364 }
2365 }
2366
ReadMap()2367 void Guest::ReadMap()
2368 {
2369 if (IsActionInterruptable())
2370 {
2371 Action = PeepActionType::ReadMap;
2372 ActionFrame = 0;
2373 ActionSpriteImageOffset = 0;
2374 UpdateCurrentActionSpriteType();
2375 }
2376 }
2377
peep_has_voucher_for_free_ride(Guest * peep,Ride * ride)2378 static bool peep_has_voucher_for_free_ride(Guest* peep, Ride* ride)
2379 {
2380 return peep->HasItem(ShopItem::Voucher) && peep->VoucherType == VOUCHER_TYPE_RIDE_FREE && peep->VoucherRideId == ride->id;
2381 }
2382
2383 /**
2384 * When the queue is full, peeps will ignore the ride when thinking about what to go on next.
2385 * Does not effect peeps that walk up to the queue entrance.
2386 * This flag is reset the next time a peep successfully joins the queue.
2387 */
peep_tried_to_enter_full_queue(Guest * peep,Ride * ride)2388 static void peep_tried_to_enter_full_queue(Guest* peep, Ride* ride)
2389 {
2390 ride->lifecycle_flags |= RIDE_LIFECYCLE_QUEUE_FULL;
2391 peep->PreviousRide = ride->id;
2392 peep->PreviousRideTimeOut = 0;
2393 // Change status "Heading to" to "Walking" if queue is full
2394 if (ride->id == peep->GuestHeadingToRideId)
2395 {
2396 peep_reset_ride_heading(peep);
2397 }
2398 }
2399
peep_reset_ride_heading(Guest * peep)2400 static void peep_reset_ride_heading(Guest* peep)
2401 {
2402 peep->GuestHeadingToRideId = RIDE_ID_NULL;
2403 peep->WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_ACTION;
2404 }
2405
peep_ride_is_too_intense(Guest * peep,Ride * ride,bool peepAtRide)2406 static void peep_ride_is_too_intense(Guest* peep, Ride* ride, bool peepAtRide)
2407 {
2408 if (peepAtRide)
2409 {
2410 peep->InsertNewThought(PeepThoughtType::Intense, ride->id);
2411 if (peep->HappinessTarget >= 64)
2412 {
2413 peep->HappinessTarget -= 8;
2414 }
2415 ride_update_popularity(ride, 0);
2416 }
2417 peep->ChoseNotToGoOnRide(ride, peepAtRide, true);
2418 }
2419
2420 /**
2421 *
2422 * rct2: 0x00691C6E
2423 */
peep_choose_car_from_ride(Peep * peep,Ride * ride,std::vector<uint8_t> & car_array)2424 static Vehicle* peep_choose_car_from_ride(Peep* peep, Ride* ride, std::vector<uint8_t>& car_array)
2425 {
2426 uint8_t chosen_car = scenario_rand();
2427 if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_G_FORCES) && ((chosen_car & 0xC) != 0xC))
2428 {
2429 chosen_car = (scenario_rand() & 1) ? 0 : static_cast<uint8_t>(car_array.size()) - 1;
2430 }
2431 else
2432 {
2433 chosen_car = (chosen_car * static_cast<uint16_t>(car_array.size())) >> 8;
2434 }
2435
2436 peep->CurrentCar = car_array[chosen_car];
2437
2438 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[peep->CurrentTrain]);
2439 if (vehicle == nullptr)
2440 {
2441 return nullptr;
2442 }
2443 return vehicle->GetCar(peep->CurrentCar);
2444 }
2445
2446 /**
2447 *
2448 * rct2: 0x00691CD1
2449 */
peep_choose_seat_from_car(Peep * peep,Ride * ride,Vehicle * vehicle)2450 static void peep_choose_seat_from_car(Peep* peep, Ride* ride, Vehicle* vehicle)
2451 {
2452 if (vehicle == nullptr)
2453 {
2454 return;
2455 }
2456 uint8_t chosen_seat = vehicle->next_free_seat;
2457
2458 if (ride->mode == RideMode::ForwardRotation || ride->mode == RideMode::BackwardRotation)
2459 {
2460 chosen_seat = (((~vehicle->Pitch + 1) >> 3) & 0xF) * 2;
2461 if (vehicle->next_free_seat & 1)
2462 {
2463 chosen_seat++;
2464 }
2465 }
2466 peep->CurrentSeat = chosen_seat;
2467 vehicle->next_free_seat++;
2468
2469 vehicle->peep[peep->CurrentSeat] = peep->sprite_index;
2470 vehicle->peep_tshirt_colours[peep->CurrentSeat] = peep->TshirtColour;
2471 }
2472
2473 /**
2474 *
2475 * rct2: 0x00691D27
2476 */
GoToRideEntrance(Ride * ride)2477 void Guest::GoToRideEntrance(Ride* ride)
2478 {
2479 TileCoordsXYZD tileLocation = ride_get_entrance_location(ride, CurrentRideStation);
2480 if (tileLocation.IsNull())
2481 {
2482 RemoveFromQueue();
2483 return;
2484 }
2485
2486 auto location = tileLocation.ToCoordsXYZD().ToTileCentre();
2487 int16_t x_shift = DirectionOffsets[location.direction].x;
2488 int16_t y_shift = DirectionOffsets[location.direction].y;
2489
2490 uint8_t shift_multiplier = 21;
2491 rct_ride_entry* rideEntry = get_ride_entry(ride->subtype);
2492 if (rideEntry != nullptr)
2493 {
2494 if (rideEntry->vehicles[rideEntry->default_vehicle].flags & VEHICLE_ENTRY_FLAG_MINI_GOLF
2495 || rideEntry->vehicles[rideEntry->default_vehicle].flags
2496 & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
2497 {
2498 shift_multiplier = 32;
2499 }
2500 }
2501
2502 x_shift *= shift_multiplier;
2503 y_shift *= shift_multiplier;
2504
2505 location.x += x_shift;
2506 location.y += y_shift;
2507
2508 SetDestination(location, 2);
2509 SetState(PeepState::EnteringRide);
2510 RideSubState = PeepRideSubState::InEntrance;
2511
2512 RejoinQueueTimeout = 0;
2513 GuestTimeOnRide = 0;
2514
2515 RemoveFromQueue();
2516 }
2517
FindVehicleToEnter(Ride * ride,std::vector<uint8_t> & car_array)2518 bool Guest::FindVehicleToEnter(Ride* ride, std::vector<uint8_t>& car_array)
2519 {
2520 uint8_t chosen_train = RideStation::NO_TRAIN;
2521
2522 if (ride->mode == RideMode::Dodgems || ride->mode == RideMode::Race)
2523 {
2524 if (ride->lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING)
2525 return false;
2526
2527 for (int32_t i = 0; i < ride->num_vehicles; ++i)
2528 {
2529 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[i]);
2530 if (vehicle == nullptr)
2531 continue;
2532
2533 if (vehicle->next_free_seat >= vehicle->num_seats)
2534 continue;
2535
2536 if (vehicle->status != Vehicle::Status::WaitingForPassengers)
2537 continue;
2538 chosen_train = i;
2539 break;
2540 }
2541 }
2542 else
2543 {
2544 chosen_train = ride->stations[CurrentRideStation].TrainAtStation;
2545 }
2546 if (chosen_train == RideStation::NO_TRAIN || chosen_train >= MAX_VEHICLES_PER_RIDE)
2547 {
2548 return false;
2549 }
2550
2551 CurrentTrain = chosen_train;
2552
2553 int32_t i = 0;
2554
2555 uint16_t vehicle_id = ride->vehicles[chosen_train];
2556 for (Vehicle* vehicle = GetEntity<Vehicle>(vehicle_id); vehicle != nullptr;
2557 vehicle = GetEntity<Vehicle>(vehicle->next_vehicle_on_train), ++i)
2558 {
2559 uint8_t num_seats = vehicle->num_seats;
2560 if (vehicle->IsUsedInPairs())
2561 {
2562 if (vehicle->next_free_seat & 1)
2563 {
2564 car_array.clear();
2565 car_array.push_back(i);
2566 return true;
2567 }
2568 num_seats &= VEHICLE_SEAT_NUM_MASK;
2569 }
2570 if (num_seats == vehicle->next_free_seat)
2571 continue;
2572
2573 if (ride->mode == RideMode::ForwardRotation || ride->mode == RideMode::BackwardRotation)
2574 {
2575 uint8_t position = (((~vehicle->Pitch + 1) >> 3) & 0xF) * 2;
2576 if (vehicle->peep[position] != SPRITE_INDEX_NULL)
2577 continue;
2578 }
2579 car_array.push_back(i);
2580 }
2581
2582 return !car_array.empty();
2583 }
2584
peep_update_ride_at_entrance_try_leave(Guest * peep)2585 static void peep_update_ride_at_entrance_try_leave(Guest* peep)
2586 {
2587 // Destination Tolerance is zero when peep has completely
2588 // entered entrance
2589 if (peep->DestinationTolerance == 0)
2590 {
2591 peep->RemoveFromQueue();
2592 peep->SetState(PeepState::Falling);
2593 }
2594 }
2595
peep_check_ride_price_at_entrance(Guest * peep,Ride * ride,money32 ridePrice)2596 static bool peep_check_ride_price_at_entrance(Guest* peep, Ride* ride, money32 ridePrice)
2597 {
2598 if ((peep->HasItem(ShopItem::Voucher)) && peep->VoucherType == VOUCHER_TYPE_RIDE_FREE
2599 && peep->VoucherRideId == peep->CurrentRide)
2600 return true;
2601
2602 if (peep->CashInPocket <= 0 && !(gParkFlags & PARK_FLAGS_NO_MONEY))
2603 {
2604 peep->InsertNewThought(PeepThoughtType::SpentMoney);
2605 peep_update_ride_at_entrance_try_leave(peep);
2606 return false;
2607 }
2608
2609 if (ridePrice > peep->CashInPocket)
2610 {
2611 peep->InsertNewThought(PeepThoughtType::CantAffordRide, peep->CurrentRide);
2612 peep_update_ride_at_entrance_try_leave(peep);
2613 return false;
2614 }
2615
2616 uint16_t value = ride->value;
2617 if (value != RIDE_VALUE_UNDEFINED)
2618 {
2619 if (value * 2 < ridePrice)
2620 {
2621 peep->InsertNewThought(PeepThoughtType::BadValue, peep->CurrentRide);
2622 peep_update_ride_at_entrance_try_leave(peep);
2623 return false;
2624 }
2625 }
2626 return true;
2627 }
2628
2629 /**
2630 * The satisfaction values calculated here are used to determine how happy the peep is with the ride,
2631 * and also affects the satisfaction stat of the ride itself. The factors that affect satisfaction include:
2632 * - The price of the ride compared to the ride's value
2633 * - How closely the intensity and nausea of the ride matches the peep's preferences
2634 * - How long the peep was waiting in the queue
2635 * - If the peep has been on the ride before, or on another ride of the same type
2636 */
peep_calculate_ride_satisfaction(Guest * peep,Ride * ride)2637 static int16_t peep_calculate_ride_satisfaction(Guest* peep, Ride* ride)
2638 {
2639 int16_t satisfaction = peep_calculate_ride_value_satisfaction(peep, ride);
2640 satisfaction += peep_calculate_ride_intensity_nausea_satisfaction(peep, ride);
2641
2642 // Calculate satisfaction based on how long the peep has been in the queue for.
2643 // (For comparison: peeps start thinking "I've been queueing for a long time" at 3500 and
2644 // start leaving the queue at 4300.)
2645 if (peep->TimeInQueue >= 4500)
2646 satisfaction -= 35;
2647 else if (peep->TimeInQueue >= 2250)
2648 satisfaction -= 10;
2649 else if (peep->TimeInQueue <= 750)
2650 satisfaction += 10;
2651
2652 // Peeps get a small boost in satisfaction if they've been on a ride of the same type before,
2653 // and this boost is doubled if they've already been on this particular ride.
2654 if (peep->HasRiddenRideType(ride->type))
2655 satisfaction += 10;
2656
2657 if (peep->HasRidden(get_ride(peep->CurrentRide)))
2658 satisfaction += 10;
2659
2660 return satisfaction;
2661 }
2662
2663 /**
2664 * Check to see if the specified ride should become the peep's favourite.
2665 * For this, a "ride rating" is calculated based on the excitement of the ride and the peep's current happiness.
2666 * As this value cannot exceed 255, the happier the peep is, the more irrelevant the ride's excitement becomes.
2667 * Due to the minimum happiness requirement, an excitement rating of more than 3.8 has no further effect.
2668 *
2669 * If the ride rating is higher than any ride the peep has already been on and the happiness criteria is met,
2670 * the ride becomes the peep's favourite. (This doesn't happen right away, but will be updated once the peep
2671 * exits the ride.)
2672 */
peep_update_favourite_ride(Guest * peep,Ride * ride)2673 static void peep_update_favourite_ride(Guest* peep, Ride* ride)
2674 {
2675 peep->PeepFlags &= ~PEEP_FLAGS_RIDE_SHOULD_BE_MARKED_AS_FAVOURITE;
2676 uint8_t peepRideRating = std::clamp((ride->excitement / 4) + peep->Happiness, 0, PEEP_MAX_HAPPINESS);
2677 if (peepRideRating >= peep->FavouriteRideRating)
2678 {
2679 if (peep->Happiness >= 160 && peep->HappinessTarget >= 160)
2680 {
2681 peep->FavouriteRideRating = peepRideRating;
2682 peep->PeepFlags |= PEEP_FLAGS_RIDE_SHOULD_BE_MARKED_AS_FAVOURITE;
2683 }
2684 }
2685 }
2686
2687 /* rct2: 0x00695555 */
peep_calculate_ride_value_satisfaction(Guest * peep,Ride * ride)2688 static int16_t peep_calculate_ride_value_satisfaction(Guest* peep, Ride* ride)
2689 {
2690 if (gParkFlags & PARK_FLAGS_NO_MONEY)
2691 {
2692 return -30;
2693 }
2694
2695 if (ride->value == RIDE_VALUE_UNDEFINED)
2696 {
2697 return -30;
2698 }
2699
2700 money16 ridePrice = ride_get_price(ride);
2701 if (ride->value >= ridePrice)
2702 {
2703 return -5;
2704 }
2705
2706 if ((ride->value + ((ride->value * peep->Happiness) / 256)) >= ridePrice)
2707 {
2708 return -30;
2709 }
2710
2711 return 0;
2712 }
2713
2714 /**
2715 * Calculate satisfaction based on the intensity and nausea of the ride.
2716 * The best possible score from this section is achieved by having the intensity and nausea
2717 * of the ride fall exactly within the peep's preferences, but lower scores can still be achieved
2718 * if the peep's happiness is enough to offset it.
2719 */
peep_calculate_ride_intensity_nausea_satisfaction(Guest * peep,Ride * ride)2720 static int16_t peep_calculate_ride_intensity_nausea_satisfaction(Guest* peep, Ride* ride)
2721 {
2722 if (!ride_has_ratings(ride))
2723 {
2724 return 70;
2725 }
2726
2727 uint8_t intensitySatisfaction = 3;
2728 uint8_t nauseaSatisfaction = 3;
2729 ride_rating maxIntensity = peep->Intensity.GetMaximum() * 100;
2730 ride_rating minIntensity = peep->Intensity.GetMinimum() * 100;
2731 if (minIntensity <= ride->intensity && maxIntensity >= ride->intensity)
2732 {
2733 intensitySatisfaction--;
2734 }
2735 minIntensity -= peep->Happiness * 2;
2736 maxIntensity += peep->Happiness;
2737 if (minIntensity <= ride->intensity && maxIntensity >= ride->intensity)
2738 {
2739 intensitySatisfaction--;
2740 }
2741 minIntensity -= peep->Happiness * 2;
2742 maxIntensity += peep->Happiness;
2743 if (minIntensity <= ride->intensity && maxIntensity >= ride->intensity)
2744 {
2745 intensitySatisfaction--;
2746 }
2747
2748 // Although it's not shown in the interface, a peep with Average or High nausea tolerance
2749 // has a minimum preferred nausea value. (For peeps with None or Low, this is set to zero.)
2750 ride_rating minNausea = NauseaMinimumThresholds[(EnumValue(peep->NauseaTolerance) & 3)];
2751 ride_rating maxNausea = NauseaMaximumThresholds[(EnumValue(peep->NauseaTolerance) & 3)];
2752 if (minNausea <= ride->nausea && maxNausea >= ride->nausea)
2753 {
2754 nauseaSatisfaction--;
2755 }
2756 minNausea -= peep->Happiness * 2;
2757 maxNausea += peep->Happiness;
2758 if (minNausea <= ride->nausea && maxNausea >= ride->nausea)
2759 {
2760 nauseaSatisfaction--;
2761 }
2762 minNausea -= peep->Happiness * 2;
2763 maxNausea += peep->Happiness;
2764 if (minNausea <= ride->nausea && maxNausea >= ride->nausea)
2765 {
2766 nauseaSatisfaction--;
2767 }
2768
2769 uint8_t highestSatisfaction = std::max(intensitySatisfaction, nauseaSatisfaction);
2770 uint8_t lowestSatisfaction = std::min(intensitySatisfaction, nauseaSatisfaction);
2771
2772 switch (highestSatisfaction)
2773 {
2774 default:
2775 case 0:
2776 return 70;
2777 case 1:
2778 switch (lowestSatisfaction)
2779 {
2780 default:
2781 case 0:
2782 return 50;
2783 case 1:
2784 return 35;
2785 }
2786 case 2:
2787 switch (lowestSatisfaction)
2788 {
2789 default:
2790 case 0:
2791 return 35;
2792 case 1:
2793 return 20;
2794 case 2:
2795 return 10;
2796 }
2797 case 3:
2798 switch (lowestSatisfaction)
2799 {
2800 default:
2801 case 0:
2802 return -35;
2803 case 1:
2804 return -50;
2805 case 2:
2806 return -60;
2807 case 3:
2808 return -60;
2809 }
2810 }
2811 }
2812
2813 /**
2814 * Update the nausea growth of the peep based on a ride. This is calculated based on:
2815 * - The nausea rating of the ride
2816 * - Their new happiness growth rate (the higher, the less nauseous)
2817 * - How hungry the peep is (+0% nausea at 50% hunger up to +100% nausea at 100% hunger)
2818 * - The peep's nausea tolerance (Final modifier: none: 100%, low: 50%, average: 25%, high: 12.5%)
2819 */
peep_update_ride_nausea_growth(Guest * peep,Ride * ride)2820 static void peep_update_ride_nausea_growth(Guest* peep, Ride* ride)
2821 {
2822 uint32_t nauseaMultiplier = std::clamp(256 - peep->HappinessTarget, 64, 200);
2823 uint32_t nauseaGrowthRateChange = (ride->nausea * nauseaMultiplier) / 512;
2824 nauseaGrowthRateChange *= std::max(static_cast<uint8_t>(128), peep->Hunger) / 64;
2825 nauseaGrowthRateChange >>= (EnumValue(peep->NauseaTolerance) & 3);
2826 peep->NauseaTarget = static_cast<uint8_t>(std::min(peep->NauseaTarget + nauseaGrowthRateChange, 255u));
2827 }
2828
peep_should_go_on_ride_again(Guest * peep,Ride * ride)2829 static bool peep_should_go_on_ride_again(Guest* peep, Ride* ride)
2830 {
2831 if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_PEEP_WILL_RIDE_AGAIN))
2832 return false;
2833 if (!ride_has_ratings(ride))
2834 return false;
2835 if (ride->intensity > RIDE_RATING(10, 00) && !gCheatsIgnoreRideIntensity)
2836 return false;
2837 if (peep->Happiness < 180)
2838 return false;
2839 if (peep->Energy < 100)
2840 return false;
2841 if (peep->Nausea > 160)
2842 return false;
2843 if (peep->Hunger < 30)
2844 return false;
2845 if (peep->Thirst < 20)
2846 return false;
2847 if (peep->Toilet > 170)
2848 return false;
2849
2850 uint8_t r = (scenario_rand() & 0xFF);
2851 if (r <= 128)
2852 {
2853 if (peep->GuestNumRides > 7)
2854 return false;
2855 if (r > 64)
2856 return false;
2857 }
2858
2859 return true;
2860 }
2861
peep_should_preferred_intensity_increase(Guest * peep)2862 static bool peep_should_preferred_intensity_increase(Guest* peep)
2863 {
2864 if (gParkFlags & PARK_FLAGS_PREF_LESS_INTENSE_RIDES)
2865 return false;
2866 if (peep->Happiness < 200)
2867 return false;
2868
2869 return (scenario_rand() & 0xFF) >= static_cast<uint8_t>(peep->Intensity);
2870 }
2871
peep_really_liked_ride(Guest * peep,Ride * ride)2872 static bool peep_really_liked_ride(Guest* peep, Ride* ride)
2873 {
2874 if (peep->Happiness < 215)
2875 return false;
2876 if (peep->Nausea > 120)
2877 return false;
2878 if (!ride_has_ratings(ride))
2879 return false;
2880 if (ride->intensity > RIDE_RATING(10, 00) && !gCheatsIgnoreRideIntensity)
2881 return false;
2882 return true;
2883 }
2884
2885 /**
2886 *
2887 * rct2: 0x0069BC9A
2888 */
peep_assess_surroundings(int16_t centre_x,int16_t centre_y,int16_t centre_z)2889 static PeepThoughtType peep_assess_surroundings(int16_t centre_x, int16_t centre_y, int16_t centre_z)
2890 {
2891 if ((tile_element_height({ centre_x, centre_y })) > centre_z)
2892 return PeepThoughtType::None;
2893
2894 uint16_t num_scenery = 0;
2895 uint16_t num_fountains = 0;
2896 uint16_t nearby_music = 0;
2897 uint16_t num_rubbish = 0;
2898
2899 int16_t initial_x = std::max(centre_x - 160, 0);
2900 int16_t initial_y = std::max(centre_y - 160, 0);
2901 int16_t final_x = std::min(centre_x + 160, MAXIMUM_MAP_SIZE_BIG);
2902 int16_t final_y = std::min(centre_y + 160, MAXIMUM_MAP_SIZE_BIG);
2903
2904 for (int16_t x = initial_x; x < final_x; x += COORDS_XY_STEP)
2905 {
2906 for (int16_t y = initial_y; y < final_y; y += COORDS_XY_STEP)
2907 {
2908 for (auto* tileElement : TileElementsView({ x, y }))
2909 {
2910 Ride* ride;
2911
2912 switch (tileElement->GetType())
2913 {
2914 case TILE_ELEMENT_TYPE_PATH:
2915 {
2916 if (!tileElement->AsPath()->HasAddition())
2917 break;
2918
2919 auto* pathAddEntry = tileElement->AsPath()->GetAdditionEntry();
2920 if (pathAddEntry == nullptr)
2921 {
2922 return PeepThoughtType::None;
2923 }
2924 if (tileElement->AsPath()->AdditionIsGhost())
2925 break;
2926
2927 if (pathAddEntry->flags & (PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER | PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW))
2928 {
2929 num_fountains++;
2930 break;
2931 }
2932 if (tileElement->AsPath()->IsBroken())
2933 {
2934 num_rubbish++;
2935 }
2936 break;
2937 }
2938 case TILE_ELEMENT_TYPE_LARGE_SCENERY:
2939 case TILE_ELEMENT_TYPE_SMALL_SCENERY:
2940 num_scenery++;
2941 break;
2942 case TILE_ELEMENT_TYPE_TRACK:
2943 ride = get_ride(tileElement->AsTrack()->GetRideIndex());
2944 if (ride != nullptr)
2945 {
2946 if (ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC && ride->status != RideStatus::Closed
2947 && !(ride->lifecycle_flags & (RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED)))
2948 {
2949 if (ride->type == RIDE_TYPE_MERRY_GO_ROUND)
2950 {
2951 nearby_music |= 1;
2952 break;
2953 }
2954
2955 if (ride->music == MUSIC_STYLE_ORGAN)
2956 {
2957 nearby_music |= 1;
2958 break;
2959 }
2960
2961 if (ride->type == RIDE_TYPE_DODGEMS)
2962 {
2963 // Dodgems drown out music?
2964 nearby_music |= 2;
2965 }
2966 }
2967 }
2968 break;
2969 }
2970 }
2971 }
2972 }
2973
2974 for (auto litter : EntityList<Litter>())
2975 {
2976 int16_t dist_x = abs(litter->x - centre_x);
2977 int16_t dist_y = abs(litter->y - centre_y);
2978 if (std::max(dist_x, dist_y) <= 160)
2979 {
2980 num_rubbish++;
2981 }
2982 }
2983
2984 if (num_fountains >= 5 && num_rubbish < 20)
2985 return PeepThoughtType::Fountains;
2986
2987 if (num_scenery >= 40 && num_rubbish < 8)
2988 return PeepThoughtType::Scenery;
2989
2990 if (nearby_music == 1 && num_rubbish < 20)
2991 return PeepThoughtType::Music;
2992
2993 if (num_rubbish < 2 && !gCheatsDisableLittering)
2994 // if disable littering cheat is enabled, peeps will not have the "clean and tidy park" thought
2995 return PeepThoughtType::VeryClean;
2996
2997 return PeepThoughtType::None;
2998 }
2999
3000 /**
3001 *
3002 * rct2: 0x0068F9A9
3003 */
peep_update_hunger(Guest * peep)3004 static void peep_update_hunger(Guest* peep)
3005 {
3006 if (peep->Hunger >= 3)
3007 {
3008 peep->Hunger -= 2;
3009
3010 peep->EnergyTarget = std::min(peep->EnergyTarget + 2, PEEP_MAX_ENERGY_TARGET);
3011 peep->Toilet = std::min(peep->Toilet + 1, 255);
3012 }
3013 }
3014
3015 /**
3016 * Main purpose is to decide when peeps leave the park due to
3017 * low happiness, low energy and (if appropriate) low money.
3018 *
3019 * rct2: 0x0068F8CD
3020 */
peep_decide_whether_to_leave_park(Guest * peep)3021 static void peep_decide_whether_to_leave_park(Guest* peep)
3022 {
3023 if (peep->EnergyTarget >= 33)
3024 {
3025 peep->EnergyTarget -= 2;
3026 }
3027
3028 if (gClimateCurrent.Temperature >= 21 && peep->Thirst >= 5)
3029 {
3030 peep->Thirst--;
3031 }
3032
3033 if (peep->OutsideOfPark)
3034 {
3035 return;
3036 }
3037
3038 /* Peeps that are happy enough, have enough energy and
3039 * (if appropriate) have enough money will always stay
3040 * in the park. */
3041 if (!(peep->PeepFlags & PEEP_FLAGS_LEAVING_PARK))
3042 {
3043 if (gParkFlags & PARK_FLAGS_NO_MONEY)
3044 {
3045 if (peep->Energy >= 70 && peep->Happiness >= 60)
3046 {
3047 return;
3048 }
3049 }
3050 else
3051 {
3052 if (peep->Energy >= 55 && peep->Happiness >= 45 && peep->CashInPocket >= MONEY(5, 00))
3053 {
3054 return;
3055 }
3056 }
3057 }
3058
3059 // Approx 95% chance of staying in the park
3060 if ((scenario_rand() & 0xFFFF) > 3276)
3061 {
3062 return;
3063 }
3064
3065 // In the remaining 5% chance the peep leaves the park.
3066 peep_leave_park(peep);
3067 }
3068
3069 /**
3070 *
3071 * rct2: 0x0068F93E
3072 */
peep_leave_park(Guest * peep)3073 static void peep_leave_park(Guest* peep)
3074 {
3075 peep->GuestHeadingToRideId = RIDE_ID_NULL;
3076 if (peep->PeepFlags & PEEP_FLAGS_LEAVING_PARK)
3077 {
3078 if (peep->GuestIsLostCountdown < 60)
3079 {
3080 return;
3081 }
3082 }
3083 else
3084 {
3085 peep->GuestIsLostCountdown = 254;
3086 peep->PeepFlags |= PEEP_FLAGS_LEAVING_PARK;
3087 peep->PeepFlags &= ~PEEP_FLAGS_PARK_ENTRANCE_CHOSEN;
3088 }
3089
3090 peep->InsertNewThought(PeepThoughtType::GoHome);
3091
3092 rct_window* w = window_find_by_number(WC_PEEP, peep->sprite_index);
3093 if (w != nullptr)
3094 window_event_invalidate_call(w);
3095 window_invalidate_by_number(WC_PEEP, peep->sprite_index);
3096 }
3097
peep_head_for_nearest_ride(Guest * peep,bool considerOnlyCloseRides,T predicate)3098 template<typename T> static void peep_head_for_nearest_ride(Guest* peep, bool considerOnlyCloseRides, T predicate)
3099 {
3100 if (peep->State != PeepState::Sitting && peep->State != PeepState::Watching && peep->State != PeepState::Walking)
3101 {
3102 return;
3103 }
3104 if (peep->PeepFlags & PEEP_FLAGS_LEAVING_PARK)
3105 return;
3106 if (peep->x == LOCATION_NULL)
3107 return;
3108 if (peep->GuestHeadingToRideId != RIDE_ID_NULL)
3109 {
3110 auto ride = get_ride(peep->GuestHeadingToRideId);
3111 if (ride != nullptr && predicate(*ride))
3112 {
3113 return;
3114 }
3115 }
3116
3117 std::bitset<MAX_RIDES> rideConsideration;
3118 if (!considerOnlyCloseRides && (peep->HasItem(ShopItem::Map)))
3119 {
3120 // Consider all rides in the park
3121 for (const auto& ride : GetRideManager())
3122 {
3123 if (predicate(ride))
3124 {
3125 rideConsideration[EnumValue(ride.id)] = true;
3126 }
3127 }
3128 }
3129 else
3130 {
3131 // Take nearby rides into consideration
3132 constexpr auto searchRadius = 10 * 32;
3133 int32_t cx = floor2(peep->x, 32);
3134 int32_t cy = floor2(peep->y, 32);
3135 for (auto x = cx - searchRadius; x <= cx + searchRadius; x += COORDS_XY_STEP)
3136 {
3137 for (auto y = cy - searchRadius; y <= cy + searchRadius; y += COORDS_XY_STEP)
3138 {
3139 auto location = CoordsXY{ x, y };
3140 if (!map_is_location_valid(location))
3141 continue;
3142
3143 for (auto* trackElement : TileElementsView<TrackElement>(location))
3144 {
3145 auto rideIndex = trackElement->GetRideIndex();
3146 auto ride = get_ride(rideIndex);
3147 if (ride == nullptr)
3148 continue;
3149
3150 if (!predicate(*ride))
3151 continue;
3152
3153 rideConsideration[EnumValue(ride->id)] = true;
3154 }
3155 }
3156 }
3157 }
3158
3159 // Filter the considered rides
3160 ride_id_t potentialRides[MAX_RIDES];
3161 size_t numPotentialRides = 0;
3162 for (auto& ride : GetRideManager())
3163 {
3164 if (rideConsideration[EnumValue(ride.id)])
3165 {
3166 if (!(ride.lifecycle_flags & RIDE_LIFECYCLE_QUEUE_FULL))
3167 {
3168 if (peep->ShouldGoOnRide(&ride, 0, false, true))
3169 {
3170 potentialRides[numPotentialRides++] = ride.id;
3171 }
3172 }
3173 }
3174 }
3175
3176 // Pick the closest ride
3177 Ride* closestRide{};
3178 auto closestRideDistance = std::numeric_limits<int32_t>::max();
3179 for (size_t i = 0; i < numPotentialRides; i++)
3180 {
3181 auto ride = get_ride(potentialRides[i]);
3182 if (ride != nullptr)
3183 {
3184 auto rideLocation = ride->stations[0].Start;
3185 int32_t distance = abs(rideLocation.x - peep->x) + abs(rideLocation.y - peep->y);
3186 if (distance < closestRideDistance)
3187 {
3188 closestRide = ride;
3189 closestRideDistance = distance;
3190 }
3191 }
3192 }
3193 if (closestRide != nullptr)
3194 {
3195 // Head to that ride
3196 peep->GuestHeadingToRideId = closestRide->id;
3197 peep->GuestIsLostCountdown = 200;
3198 peep->ResetPathfindGoal();
3199 peep->WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_ACTION;
3200 peep->TimeLost = 0;
3201 }
3202 }
3203
peep_head_for_nearest_ride_type(Guest * peep,int32_t rideType)3204 static void peep_head_for_nearest_ride_type(Guest* peep, int32_t rideType)
3205 {
3206 auto considerOnlyCloseRides = rideType == RIDE_TYPE_FIRST_AID;
3207 return peep_head_for_nearest_ride(
3208 peep, considerOnlyCloseRides, [rideType](const Ride& ride) { return ride.type == rideType; });
3209 }
3210
peep_head_for_nearest_ride_with_flags(Guest * peep,int32_t rideTypeFlags)3211 static void peep_head_for_nearest_ride_with_flags(Guest* peep, int32_t rideTypeFlags)
3212 {
3213 if ((rideTypeFlags & RIDE_TYPE_FLAG_IS_TOILET) && peep->HasFoodOrDrink())
3214 {
3215 return;
3216 }
3217 peep_head_for_nearest_ride(
3218 peep, false, [rideTypeFlags](const Ride& ride) { return ride.GetRideTypeDescriptor().HasFlag(rideTypeFlags); });
3219 }
3220
3221 /**
3222 *
3223 * rct2: 0x00699FE3
3224 * Stops peeps that are having thoughts
3225 * such as "I'm hungry" after visiting a food shop.
3226 * Works for Thirst/Hungry/Low Money/Toilet
3227 */
StopPurchaseThought(uint8_t ride_type)3228 void Guest::StopPurchaseThought(uint8_t ride_type)
3229 {
3230 auto thoughtType = PeepThoughtType::Hungry;
3231
3232 if (!GetRideTypeDescriptor(ride_type).HasFlag(RIDE_TYPE_FLAG_SELLS_FOOD))
3233 {
3234 thoughtType = PeepThoughtType::Thirsty;
3235 if (!GetRideTypeDescriptor(ride_type).HasFlag(RIDE_TYPE_FLAG_SELLS_DRINKS))
3236 {
3237 thoughtType = PeepThoughtType::RunningOut;
3238 if (ride_type != RIDE_TYPE_CASH_MACHINE)
3239 {
3240 thoughtType = PeepThoughtType::Toilet;
3241 if (!GetRideTypeDescriptor(ride_type).HasFlag(RIDE_TYPE_FLAG_IS_TOILET))
3242 {
3243 return;
3244 }
3245 }
3246 }
3247 }
3248
3249 // Remove the related thought
3250 for (int32_t i = 0; i < PEEP_MAX_THOUGHTS; ++i)
3251 {
3252 PeepThought* thought = &Thoughts[i];
3253
3254 if (thought->type == PeepThoughtType::None)
3255 break;
3256
3257 if (thought->type != thoughtType)
3258 continue;
3259
3260 if (i < PEEP_MAX_THOUGHTS - 1)
3261 {
3262 memmove(thought, thought + 1, sizeof(PeepThought) * (PEEP_MAX_THOUGHTS - i - 1));
3263 }
3264
3265 Thoughts[PEEP_MAX_THOUGHTS - 1].type = PeepThoughtType::None;
3266
3267 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_THOUGHTS;
3268 i--;
3269 }
3270 }
3271
3272 /**
3273 *
3274 * rct2: 0x0069AEB7
3275 */
peep_should_use_cash_machine(Guest * peep,ride_id_t rideIndex)3276 static bool peep_should_use_cash_machine(Guest* peep, ride_id_t rideIndex)
3277 {
3278 if (gParkFlags & PARK_FLAGS_NO_MONEY)
3279 return false;
3280 if (peep->PeepFlags & PEEP_FLAGS_LEAVING_PARK)
3281 return false;
3282 if (peep->CashInPocket > MONEY(20, 00))
3283 return false;
3284 if (115 + (scenario_rand() % 128) > peep->Happiness)
3285 return false;
3286 if (peep->Energy < 80)
3287 return false;
3288
3289 auto ride = get_ride(rideIndex);
3290 if (ride != nullptr)
3291 {
3292 ride_update_satisfaction(ride, peep->Happiness >> 6);
3293 ride->cur_num_customers++;
3294 ride->total_customers++;
3295 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;
3296 }
3297 return true;
3298 }
3299
3300 /**
3301 *
3302 * rct2: 0x006912A3
3303 */
UpdateBuying()3304 void Guest::UpdateBuying()
3305 {
3306 if (!CheckForPath())
3307 return;
3308
3309 auto ride = get_ride(CurrentRide);
3310 if (ride == nullptr || ride->status != RideStatus::Open)
3311 {
3312 SetState(PeepState::Falling);
3313 return;
3314 }
3315
3316 if (SubState == 1)
3317 {
3318 if (!IsActionWalking())
3319 {
3320 UpdateAction();
3321 Invalidate();
3322 return;
3323 }
3324
3325 if (ride->type == RIDE_TYPE_CASH_MACHINE)
3326 {
3327 if (CurrentRide != PreviousRide)
3328 {
3329 CashInPocket += MONEY(50, 00);
3330 }
3331 window_invalidate_by_number(WC_PEEP, sprite_index);
3332 }
3333 sprite_direction ^= 0x10;
3334
3335 auto destination = CoordsXY{ 16, 16 } + NextLoc;
3336 SetDestination(destination);
3337 PeepDirection = direction_reverse(PeepDirection);
3338
3339 SetState(PeepState::Walking);
3340 return;
3341 }
3342
3343 bool item_bought = false;
3344
3345 if (CurrentRide != PreviousRide)
3346 {
3347 if (ride->type == RIDE_TYPE_CASH_MACHINE)
3348 {
3349 item_bought = peep_should_use_cash_machine(this, CurrentRide);
3350 if (!item_bought)
3351 {
3352 PreviousRide = CurrentRide;
3353 PreviousRideTimeOut = 0;
3354 }
3355 else
3356 {
3357 Action = PeepActionType::WithdrawMoney;
3358 ActionFrame = 0;
3359 ActionSpriteImageOffset = 0;
3360
3361 UpdateCurrentActionSpriteType();
3362
3363 ride->no_primary_items_sold++;
3364 }
3365 }
3366 else
3367 {
3368 rct_ride_entry* ride_type = get_ride_entry(ride->subtype);
3369 if (ride_type == nullptr)
3370 {
3371 return;
3372 }
3373 if (ride_type->shop_item[1] != ShopItem::None)
3374 {
3375 money16 price = ride->price[1];
3376
3377 item_bought = DecideAndBuyItem(ride, ride_type->shop_item[1], price);
3378 if (item_bought)
3379 {
3380 ride->no_secondary_items_sold++;
3381 }
3382 }
3383
3384 if (!item_bought && ride_type->shop_item[0] != ShopItem::None)
3385 {
3386 money16 price = ride->price[0];
3387
3388 item_bought = DecideAndBuyItem(ride, ride_type->shop_item[0], price);
3389 if (item_bought)
3390 {
3391 ride->no_primary_items_sold++;
3392 }
3393 }
3394 }
3395 }
3396
3397 if (item_bought)
3398 {
3399 ride_update_popularity(ride, 1);
3400
3401 StopPurchaseThought(ride->type);
3402 }
3403 else
3404 {
3405 ride_update_popularity(ride, 0);
3406 }
3407 SubState = 1;
3408 }
3409
3410 /**
3411 *
3412 * rct2: 0x00691A3B
3413 */
UpdateRideAtEntrance()3414 void Guest::UpdateRideAtEntrance()
3415 {
3416 auto ride = get_ride(CurrentRide);
3417 if (ride == nullptr)
3418 return;
3419
3420 // The peep will keep advancing in the entranceway
3421 // whilst in this state. When it has reached the very
3422 // front of the queue destination tolerance is set to
3423 // zero to indicate it is final decision time (try_leave will pass).
3424 // When a peep has to return to the queue without getting on a ride
3425 // this is the state it will return to.
3426 if (DestinationTolerance != 0)
3427 {
3428 int16_t xy_distance;
3429 if (auto loc = UpdateAction(xy_distance); loc.has_value())
3430 {
3431 int16_t actionZ = z;
3432 if (xy_distance < 16)
3433 {
3434 auto entrance = ride_get_entrance_location(ride, CurrentRideStation).ToCoordsXYZ();
3435 actionZ = entrance.z + 2;
3436 }
3437 MoveTo({ loc.value(), actionZ });
3438 }
3439 else
3440 {
3441 DestinationTolerance = 0;
3442 sprite_direction ^= (1 << 4);
3443 Invalidate();
3444 }
3445 }
3446
3447 std::vector<uint8_t> carArray;
3448
3449 if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
3450 {
3451 if (ride->num_riders >= ride->operation_option)
3452 return;
3453 }
3454 else
3455 {
3456 if (!FindVehicleToEnter(ride, carArray))
3457 return;
3458 }
3459
3460 if (ride->status != RideStatus::Open || ride->vehicle_change_timeout != 0)
3461 {
3462 peep_update_ride_at_entrance_try_leave(this);
3463 return;
3464 }
3465
3466 if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
3467 return;
3468
3469 money16 ridePrice = ride_get_price(ride);
3470 if (ridePrice != 0)
3471 {
3472 if (!peep_check_ride_price_at_entrance(this, ride, ridePrice))
3473 return;
3474 }
3475
3476 if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
3477 {
3478 Vehicle* vehicle = peep_choose_car_from_ride(this, ride, carArray);
3479 peep_choose_seat_from_car(this, ride, vehicle);
3480 }
3481 GoToRideEntrance(ride);
3482 }
3483
3484 /** rct2: 0x00981FD4, 0x00981FD6 */
3485 static constexpr const CoordsXY _MazeEntranceStart[] = {
3486 { 8, 8 },
3487 { 8, 24 },
3488 { 24, 24 },
3489 { 24, 8 },
3490 };
3491
peep_update_ride_leave_entrance_maze(Guest * peep,Ride * ride,CoordsXYZD & entrance_loc)3492 static void peep_update_ride_leave_entrance_maze(Guest* peep, Ride* ride, CoordsXYZD& entrance_loc)
3493 {
3494 peep->MazeLastEdge = entrance_loc.direction + 1;
3495
3496 entrance_loc.x += CoordsDirectionDelta[entrance_loc.direction].x;
3497 entrance_loc.y += CoordsDirectionDelta[entrance_loc.direction].y;
3498
3499 uint8_t direction = entrance_loc.direction * 4 + 11;
3500 if (scenario_rand() & 0x40)
3501 {
3502 direction += 4;
3503 peep->MazeLastEdge += 2;
3504 }
3505
3506 direction &= 0xF;
3507 // Direction is 11, 15, 3, or 7
3508 peep->Var37 = direction;
3509 peep->MazeLastEdge &= 3;
3510
3511 entrance_loc.x += _MazeEntranceStart[direction / 4].x;
3512 entrance_loc.y += _MazeEntranceStart[direction / 4].y;
3513
3514 peep->SetDestination(entrance_loc, 3);
3515
3516 ride->cur_num_customers++;
3517 peep->OnEnterRide(ride);
3518 peep->RideSubState = PeepRideSubState::MazePathfinding;
3519 }
3520
peep_update_ride_leave_entrance_spiral_slide(Guest * peep,Ride * ride,CoordsXYZD & entrance_loc)3521 static void peep_update_ride_leave_entrance_spiral_slide(Guest* peep, Ride* ride, CoordsXYZD& entrance_loc)
3522 {
3523 entrance_loc = { ride->stations[peep->CurrentRideStation].GetStart(), entrance_loc.direction };
3524
3525 TileElement* tile_element = ride_get_station_start_track_element(ride, peep->CurrentRideStation);
3526
3527 uint8_t direction_track = (tile_element == nullptr ? 0 : tile_element->GetDirection());
3528
3529 peep->Var37 = (entrance_loc.direction << 2) | (direction_track << 4);
3530
3531 entrance_loc += SpiralSlideWalkingPath[peep->Var37];
3532
3533 peep->SetDestination(entrance_loc);
3534 peep->CurrentCar = 0;
3535
3536 ride->cur_num_customers++;
3537 peep->OnEnterRide(ride);
3538 peep->RideSubState = PeepRideSubState::ApproachSpiralSlide;
3539 }
3540
GetWaypointedSeatLocation(const Ride & ride,rct_ride_entry_vehicle * vehicle_type,uint8_t track_direction) const3541 uint8_t Guest::GetWaypointedSeatLocation(const Ride& ride, rct_ride_entry_vehicle* vehicle_type, uint8_t track_direction) const
3542 {
3543 // The seatlocation can be split into segments around the ride base
3544 // to decide the segment first split off the segmentable seat location
3545 // from the fixed section
3546 uint8_t seatLocationSegment = CurrentSeat & 0x7;
3547 uint8_t seatLocationFixed = CurrentSeat & 0xF8;
3548
3549 // Enterprise has more segments (8) compared to the normal (4)
3550 if (ride.type != RIDE_TYPE_ENTERPRISE)
3551 track_direction *= 2;
3552
3553 // Type 1 loading doesn't do segments and all peeps go to the same
3554 // location on the ride
3555 if (vehicle_type->peep_loading_waypoint_segments == 0)
3556 {
3557 track_direction /= 2;
3558 seatLocationSegment = 0;
3559 seatLocationFixed = 0;
3560 }
3561 seatLocationSegment += track_direction;
3562 seatLocationSegment &= 0x7;
3563 return seatLocationSegment + seatLocationFixed;
3564 }
3565
UpdateRideLeaveEntranceWaypoints(const Ride & ride)3566 void Guest::UpdateRideLeaveEntranceWaypoints(const Ride& ride)
3567 {
3568 TileCoordsXYZD entranceLocation = ride_get_entrance_location(&ride, CurrentRideStation);
3569 Guard::Assert(!entranceLocation.IsNull());
3570 uint8_t direction_entrance = entranceLocation.direction;
3571
3572 CoordsXY waypoint = ride.stations[CurrentRideStation].Start.ToTileCentre();
3573
3574 TileElement* tile_element = ride_get_station_start_track_element(&ride, CurrentRideStation);
3575
3576 uint8_t direction_track = (tile_element == nullptr ? 0 : tile_element->GetDirection());
3577
3578 auto vehicle = GetEntity<Vehicle>(ride.vehicles[CurrentTrain]);
3579 if (vehicle == nullptr)
3580 {
3581 // TODO: Goto ride exit on failure.
3582 return;
3583 }
3584 auto ride_entry = vehicle->GetRideEntry();
3585 auto vehicle_type = &ride_entry->vehicles[vehicle->vehicle_type];
3586
3587 Var37 = (direction_entrance | GetWaypointedSeatLocation(ride, vehicle_type, direction_track) * 4) * 4;
3588
3589 if (ride.type == RIDE_TYPE_ENTERPRISE)
3590 {
3591 waypoint.x = vehicle->x;
3592 waypoint.y = vehicle->y;
3593 }
3594
3595 const auto waypointIndex = Var37 / 4;
3596 Guard::Assert(vehicle_type->peep_loading_waypoints.size() >= static_cast<size_t>(waypointIndex));
3597 waypoint.x += vehicle_type->peep_loading_waypoints[waypointIndex][0].x;
3598 waypoint.y += vehicle_type->peep_loading_waypoints[waypointIndex][0].y;
3599
3600 SetDestination(waypoint);
3601 RideSubState = PeepRideSubState::ApproachVehicleWaypoints;
3602 }
3603
3604 /**
3605 *
3606 * rct2: 0x006921D3
3607 */
UpdateRideAdvanceThroughEntrance()3608 void Guest::UpdateRideAdvanceThroughEntrance()
3609 {
3610 auto ride = get_ride(CurrentRide);
3611 if (ride == nullptr)
3612 return;
3613
3614 int16_t actionZ, xy_distance;
3615
3616 auto ride_entry = ride->GetRideEntry();
3617
3618 if (auto loc = UpdateAction(xy_distance); loc.has_value())
3619 {
3620 uint16_t distanceThreshold = 16;
3621 if (ride_entry != nullptr)
3622 {
3623 uint8_t vehicle = ride_entry->default_vehicle;
3624 if (ride_entry->vehicles[vehicle].flags & VEHICLE_ENTRY_FLAG_MINI_GOLF
3625 || ride_entry->vehicles[vehicle].flags & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
3626 {
3627 distanceThreshold = 28;
3628 }
3629 }
3630
3631 if (RideSubState == PeepRideSubState::InEntrance && xy_distance < distanceThreshold)
3632 {
3633 RideSubState = PeepRideSubState::FreeVehicleCheck;
3634 }
3635
3636 actionZ = ride->stations[CurrentRideStation].GetBaseZ();
3637
3638 distanceThreshold += 4;
3639 if (xy_distance < distanceThreshold)
3640 {
3641 actionZ += ride->GetRideTypeDescriptor().Heights.PlatformHeight;
3642 }
3643
3644 MoveTo({ loc.value(), actionZ });
3645 return;
3646 }
3647
3648 Guard::Assert(RideSubState == PeepRideSubState::LeaveEntrance, "Peep ridesubstate should be LeaveEntrance");
3649 if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
3650 {
3651 auto entranceLocation = ride_get_entrance_location(ride, CurrentRideStation).ToCoordsXYZD();
3652 Guard::Assert(!entranceLocation.IsNull());
3653
3654 if (ride->type == RIDE_TYPE_MAZE)
3655 {
3656 peep_update_ride_leave_entrance_maze(this, ride, entranceLocation);
3657 return;
3658 }
3659 if (ride->type == RIDE_TYPE_SPIRAL_SLIDE)
3660 {
3661 peep_update_ride_leave_entrance_spiral_slide(this, ride, entranceLocation);
3662 return;
3663 }
3664
3665 // If the ride type was changed guests will become stuck.
3666 // Inform the player about this if its a new issue or hasn't been addressed within 120 seconds.
3667 if ((ride->current_issues & RIDE_ISSUE_GUESTS_STUCK) == 0 || gCurrentTicks - ride->last_issue_time > 3000)
3668 {
3669 ride->current_issues |= RIDE_ISSUE_GUESTS_STUCK;
3670 ride->last_issue_time = gCurrentTicks;
3671
3672 auto ft = Formatter();
3673 ride->FormatNameTo(ft);
3674 if (gConfigNotifications.ride_warnings)
3675 {
3676 News::AddItemToQueue(News::ItemType::Ride, STR_GUESTS_GETTING_STUCK_ON_RIDE, EnumValue(CurrentRide), ft);
3677 }
3678 }
3679
3680 return;
3681 }
3682
3683 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[CurrentTrain]);
3684 if (vehicle == nullptr)
3685 {
3686 return;
3687 }
3688
3689 vehicle = vehicle->GetCar(CurrentCar);
3690 if (vehicle == nullptr)
3691 {
3692 return;
3693 }
3694
3695 ride_entry = vehicle->GetRideEntry();
3696 if (ride_entry == nullptr)
3697 {
3698 return;
3699 }
3700
3701 rct_ride_entry_vehicle* vehicle_type = &ride_entry->vehicles[vehicle->vehicle_type];
3702
3703 if (vehicle_type->flags & VEHICLE_ENTRY_FLAG_LOADING_WAYPOINTS)
3704 {
3705 UpdateRideLeaveEntranceWaypoints(*ride);
3706 return;
3707 }
3708
3709 if (vehicle_type->flags & VEHICLE_ENTRY_FLAG_DODGEM_CAR_PLACEMENT)
3710 {
3711 SetDestination(vehicle->GetLocation(), 15);
3712 RideSubState = PeepRideSubState::ApproachVehicle;
3713 return;
3714 }
3715
3716 int8_t load_position = 0;
3717 // Safe, in case current seat > number of loading positions
3718 uint16_t numSeatPositions = static_cast<uint16_t>(vehicle_type->peep_loading_positions.size());
3719 if (numSeatPositions != 0)
3720 {
3721 size_t loadPositionIndex = numSeatPositions - 1;
3722 if (CurrentSeat < numSeatPositions)
3723 {
3724 loadPositionIndex = CurrentSeat;
3725 }
3726 load_position = vehicle_type->peep_loading_positions[loadPositionIndex];
3727 }
3728
3729 auto destination = GetDestination();
3730 switch (vehicle->sprite_direction / 8)
3731 {
3732 case 0:
3733 destination.x = vehicle->x - load_position;
3734 break;
3735 case 1:
3736 destination.y = vehicle->y + load_position;
3737 break;
3738 case 2:
3739 destination.x = vehicle->x + load_position;
3740 break;
3741 case 3:
3742 destination.y = vehicle->y - load_position;
3743 break;
3744 }
3745 SetDestination(destination);
3746
3747 RideSubState = PeepRideSubState::ApproachVehicle;
3748 }
3749
3750 /**
3751 *
3752 * rct2: 0x0069321D
3753 */
peep_go_to_ride_exit(Peep * peep,Ride * ride,int16_t x,int16_t y,int16_t z,uint8_t exit_direction)3754 static void peep_go_to_ride_exit(Peep* peep, Ride* ride, int16_t x, int16_t y, int16_t z, uint8_t exit_direction)
3755 {
3756 z += ride->GetRideTypeDescriptor().Heights.PlatformHeight;
3757
3758 peep->MoveTo({ x, y, z });
3759
3760 Guard::Assert(peep->CurrentRideStation < MAX_STATIONS);
3761 auto exit = ride_get_exit_location(ride, peep->CurrentRideStation);
3762 Guard::Assert(!exit.IsNull());
3763 x = exit.x;
3764 y = exit.y;
3765 x *= 32;
3766 y *= 32;
3767 x += 16;
3768 y += 16;
3769
3770 int16_t x_shift = DirectionOffsets[exit_direction].x;
3771 int16_t y_shift = DirectionOffsets[exit_direction].y;
3772
3773 int16_t shift_multiplier = 20;
3774
3775 rct_ride_entry* rideEntry = get_ride_entry(ride->subtype);
3776 if (rideEntry != nullptr)
3777 {
3778 rct_ride_entry_vehicle* vehicle_entry = &rideEntry->vehicles[rideEntry->default_vehicle];
3779 if (vehicle_entry->flags & VEHICLE_ENTRY_FLAG_MINI_GOLF
3780 || vehicle_entry->flags & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
3781 {
3782 shift_multiplier = 32;
3783 }
3784 }
3785
3786 x_shift *= shift_multiplier;
3787 y_shift *= shift_multiplier;
3788
3789 x -= x_shift;
3790 y -= y_shift;
3791
3792 peep->SetDestination({ x, y }, 2);
3793
3794 peep->sprite_direction = exit_direction * 8;
3795 peep->RideSubState = PeepRideSubState::ApproachExit;
3796 }
3797
3798 /**
3799 *
3800 * rct2: 0x006920B4
3801 */
UpdateRideFreeVehicleEnterRide(Ride * ride)3802 void Guest::UpdateRideFreeVehicleEnterRide(Ride* ride)
3803 {
3804 money16 ridePrice = ride_get_price(ride);
3805 if (ridePrice != 0)
3806 {
3807 if ((HasItem(ShopItem::Voucher)) && (VoucherType == VOUCHER_TYPE_RIDE_FREE) && (VoucherRideId == CurrentRide))
3808 {
3809 RemoveItem(ShopItem::Voucher);
3810 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_INVENTORY;
3811 }
3812 else
3813 {
3814 ride->total_profit += ridePrice;
3815 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_INCOME;
3816 SpendMoney(PaidOnRides, ridePrice, ExpenditureType::ParkRideTickets);
3817 }
3818 }
3819
3820 RideSubState = PeepRideSubState::LeaveEntrance;
3821 uint8_t queueTime = DaysInQueue;
3822 if (queueTime < 253)
3823 queueTime += 3;
3824
3825 queueTime /= 2;
3826 if (queueTime != ride->stations[CurrentRideStation].QueueTime)
3827 {
3828 ride->stations[CurrentRideStation].QueueTime = queueTime;
3829 window_invalidate_by_number(WC_RIDE, EnumValue(CurrentRide));
3830 }
3831
3832 if (PeepFlags & PEEP_FLAGS_TRACKING)
3833 {
3834 auto ft = Formatter();
3835 FormatNameTo(ft);
3836 ride->FormatNameTo(ft);
3837
3838 rct_string_id msg_string;
3839 if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IN_RIDE))
3840 msg_string = STR_PEEP_TRACKING_PEEP_IS_IN_X;
3841 else
3842 msg_string = STR_PEEP_TRACKING_PEEP_IS_ON_X;
3843
3844 if (gConfigNotifications.guest_on_ride)
3845 {
3846 News::AddItemToQueue(News::ItemType::PeepOnRide, msg_string, sprite_index, ft);
3847 }
3848 }
3849
3850 if (ride->type == RIDE_TYPE_SPIRAL_SLIDE)
3851 {
3852 SwitchToSpecialSprite(1);
3853 }
3854
3855 UpdateRideAdvanceThroughEntrance();
3856 }
3857
3858 /**
3859 *
3860 * rct2: 0x00691FD4
3861 */
peep_update_ride_no_free_vehicle_rejoin_queue(Guest * peep,Ride * ride)3862 static void peep_update_ride_no_free_vehicle_rejoin_queue(Guest* peep, Ride* ride)
3863 {
3864 TileCoordsXYZD entranceLocation = ride_get_entrance_location(ride, peep->CurrentRideStation);
3865
3866 int32_t x = entranceLocation.x * 32;
3867 int32_t y = entranceLocation.y * 32;
3868 x += 16 - DirectionOffsets[entranceLocation.direction].x * 20;
3869 y += 16 - DirectionOffsets[entranceLocation.direction].y * 20;
3870
3871 peep->SetDestination({ x, y }, 2);
3872 peep->SetState(PeepState::QueuingFront);
3873 peep->RideSubState = PeepRideSubState::AtEntrance;
3874
3875 ride->QueueInsertGuestAtFront(peep->CurrentRideStation, peep);
3876 }
3877
3878 /**
3879 *
3880 * rct2: 0x00691E42
3881 * Note: Before this was the entry
3882 * point for sub state 1 and 3. The
3883 * check has been removed that would
3884 * branch it out to 1 and 3. Now uses
3885 * separate functions.
3886 */
UpdateRideFreeVehicleCheck()3887 void Guest::UpdateRideFreeVehicleCheck()
3888 {
3889 auto ride = get_ride(CurrentRide);
3890 if (ride == nullptr)
3891 return;
3892
3893 if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES))
3894 {
3895 if (ride->status != RideStatus::Open || ride->vehicle_change_timeout != 0 || (++RejoinQueueTimeout) == 0)
3896 {
3897 peep_update_ride_no_free_vehicle_rejoin_queue(this, ride);
3898 return;
3899 }
3900
3901 UpdateRideFreeVehicleEnterRide(ride);
3902 return;
3903 }
3904
3905 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[CurrentTrain]);
3906 if (vehicle == nullptr)
3907 {
3908 // TODO: Leave ride on failure goes for all returns on nullptr in this function
3909 return;
3910 }
3911 vehicle = vehicle->GetCar(CurrentCar);
3912 if (vehicle == nullptr)
3913 return;
3914
3915 rct_ride_entry* ride_entry = vehicle->GetRideEntry();
3916 if (ride_entry == nullptr)
3917 {
3918 return;
3919 }
3920
3921 if (ride_entry->vehicles[0].flags & VEHICLE_ENTRY_FLAG_MINI_GOLF)
3922 {
3923 vehicle->mini_golf_flags &= ~MiniGolfFlag::Flag5;
3924
3925 for (size_t i = 0; i < ride->num_vehicles; ++i)
3926 {
3927 Vehicle* train = GetEntity<Vehicle>(ride->vehicles[i]);
3928 if (train == nullptr)
3929 continue;
3930
3931 Vehicle* second_vehicle = GetEntity<Vehicle>(train->next_vehicle_on_train);
3932 if (second_vehicle == nullptr)
3933 continue;
3934
3935 if (second_vehicle->num_peeps == 0)
3936 continue;
3937
3938 if (second_vehicle->mini_golf_flags & MiniGolfFlag::Flag5)
3939 continue;
3940
3941 return;
3942 }
3943 }
3944
3945 if (!vehicle->IsUsedInPairs())
3946 {
3947 UpdateRideFreeVehicleEnterRide(ride);
3948 return;
3949 }
3950
3951 if (ride->mode == RideMode::ForwardRotation || ride->mode == RideMode::BackwardRotation)
3952 {
3953 if (CurrentSeat & 1 || !(vehicle->next_free_seat & 1))
3954 {
3955 UpdateRideFreeVehicleEnterRide(ride);
3956 return;
3957 }
3958 }
3959 else
3960 {
3961 uint8_t seat = CurrentSeat | 1;
3962 if (seat < vehicle->next_free_seat)
3963 {
3964 UpdateRideFreeVehicleEnterRide(ride);
3965 return;
3966 }
3967 }
3968
3969 Vehicle* currentTrain = GetEntity<Vehicle>(ride->vehicles[CurrentTrain]);
3970 if (currentTrain == nullptr)
3971 {
3972 return;
3973 }
3974 if (ride->status == RideStatus::Open && ++RejoinQueueTimeout != 0
3975 && !currentTrain->HasUpdateFlag(VEHICLE_UPDATE_FLAG_TRAIN_READY_DEPART))
3976 {
3977 return;
3978 }
3979
3980 if (ride->mode != RideMode::ForwardRotation && ride->mode != RideMode::BackwardRotation)
3981 {
3982 if (vehicle->next_free_seat - 1 != CurrentSeat)
3983 return;
3984 }
3985
3986 vehicle->next_free_seat--;
3987 vehicle->peep[CurrentSeat] = SPRITE_INDEX_NULL;
3988
3989 peep_update_ride_no_free_vehicle_rejoin_queue(this, ride);
3990 }
3991
UpdateRideApproachVehicle()3992 void Guest::UpdateRideApproachVehicle()
3993 {
3994 if (auto loc = UpdateAction(); loc.has_value())
3995 {
3996 MoveTo({ loc.value(), z });
3997 return;
3998 }
3999 RideSubState = PeepRideSubState::EnterVehicle;
4000 }
4001
UpdateRideEnterVehicle()4002 void Guest::UpdateRideEnterVehicle()
4003 {
4004 auto* ride = get_ride(CurrentRide);
4005 if (ride != nullptr)
4006 {
4007 auto* vehicle = GetEntity<Vehicle>(ride->vehicles[CurrentTrain]);
4008 if (vehicle != nullptr)
4009 {
4010 vehicle = vehicle->GetCar(CurrentCar);
4011 if (vehicle == nullptr)
4012 {
4013 return;
4014 }
4015
4016 if (ride->mode != RideMode::ForwardRotation && ride->mode != RideMode::BackwardRotation)
4017 {
4018 if (CurrentSeat != vehicle->num_peeps)
4019 return;
4020 }
4021
4022 if (vehicle->IsUsedInPairs())
4023 {
4024 auto* seatedGuest = GetEntity<Guest>(vehicle->peep[CurrentSeat ^ 1]);
4025 if (seatedGuest != nullptr)
4026 {
4027 if (seatedGuest->RideSubState != PeepRideSubState::EnterVehicle)
4028 return;
4029
4030 vehicle->num_peeps++;
4031 ride->cur_num_customers++;
4032
4033 vehicle->ApplyMass(seatedGuest->Mass);
4034 seatedGuest->MoveTo({ LOCATION_NULL, 0, 0 });
4035 seatedGuest->SetState(PeepState::OnRide);
4036 seatedGuest->GuestTimeOnRide = 0;
4037 seatedGuest->RideSubState = PeepRideSubState::OnRide;
4038 seatedGuest->OnEnterRide(ride);
4039 }
4040 }
4041
4042 vehicle->num_peeps++;
4043 ride->cur_num_customers++;
4044
4045 vehicle->ApplyMass(Mass);
4046 vehicle->Invalidate();
4047
4048 MoveTo({ LOCATION_NULL, 0, 0 });
4049
4050 SetState(PeepState::OnRide);
4051
4052 GuestTimeOnRide = 0;
4053 RideSubState = PeepRideSubState::OnRide;
4054 OnEnterRide(ride);
4055 }
4056 }
4057 }
4058
4059 /**
4060 *
4061 * rct2: 0x00693028
4062 */
UpdateRideLeaveVehicle()4063 void Guest::UpdateRideLeaveVehicle()
4064 {
4065 auto ride = get_ride(CurrentRide);
4066 if (ride == nullptr)
4067 return;
4068
4069 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[CurrentTrain]);
4070 if (vehicle == nullptr)
4071 return;
4072
4073 uint8_t ride_station = vehicle->current_station;
4074 vehicle = vehicle->GetCar(CurrentCar);
4075 if (vehicle == nullptr)
4076 {
4077 return;
4078 }
4079
4080 // Check if ride is NOT Ferris Wheel.
4081 if (ride->mode != RideMode::ForwardRotation && ride->mode != RideMode::BackwardRotation)
4082 {
4083 if (vehicle->num_peeps - 1 != CurrentSeat)
4084 return;
4085 }
4086
4087 ActionSpriteImageOffset++;
4088 if (ActionSpriteImageOffset & 3)
4089 return;
4090
4091 ActionSpriteImageOffset = 0;
4092
4093 vehicle->num_peeps--;
4094 vehicle->ApplyMass(-Mass);
4095 vehicle->Invalidate();
4096
4097 if (ride_station >= MAX_STATIONS)
4098 {
4099 // HACK #5658: Some parks have hacked rides which end up in this state
4100 auto bestStationIndex = ride_get_first_valid_station_exit(ride);
4101 if (bestStationIndex == STATION_INDEX_NULL)
4102 {
4103 bestStationIndex = 0;
4104 }
4105 ride_station = bestStationIndex;
4106 }
4107 CurrentRideStation = ride_station;
4108 rct_ride_entry* rideEntry = vehicle->GetRideEntry();
4109 if (rideEntry == nullptr)
4110 {
4111 return;
4112 }
4113
4114 rct_ride_entry_vehicle* vehicle_entry = &rideEntry->vehicles[vehicle->vehicle_type];
4115
4116 if (!(vehicle_entry->flags & VEHICLE_ENTRY_FLAG_LOADING_WAYPOINTS))
4117 {
4118 assert(CurrentRideStation < MAX_STATIONS);
4119 TileCoordsXYZD exitLocation = ride_get_exit_location(ride, CurrentRideStation);
4120 CoordsXYZD platformLocation;
4121 platformLocation.z = ride->stations[CurrentRideStation].GetBaseZ();
4122
4123 platformLocation.direction = direction_reverse(exitLocation.direction);
4124
4125 if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_VEHICLE_IS_INTEGRAL))
4126 {
4127 for (; vehicle != nullptr && !vehicle->IsHead(); vehicle = GetEntity<Vehicle>(vehicle->prev_vehicle_on_ride))
4128 {
4129 auto trackType = vehicle->GetTrackType();
4130 if (trackType == TrackElemType::Flat || trackType > TrackElemType::MiddleStation)
4131 continue;
4132
4133 bool foundStation = false;
4134 for (auto* trackElement : TileElementsView<TrackElement>(vehicle->TrackLocation))
4135 {
4136 if (trackElement->GetBaseZ() != vehicle->TrackLocation.z)
4137 continue;
4138
4139 if (trackElement->GetStationIndex() != CurrentRideStation)
4140 continue;
4141
4142 foundStation = true;
4143 break;
4144 }
4145
4146 if (foundStation)
4147 break;
4148 }
4149
4150 if (vehicle == nullptr)
4151 {
4152 return;
4153 }
4154 uint8_t shiftMultiplier = 12;
4155 uint8_t specialDirection = platformLocation.direction;
4156
4157 rideEntry = get_ride_entry(ride->subtype);
4158
4159 if (rideEntry != nullptr)
4160 {
4161 vehicle_entry = &rideEntry->vehicles[rideEntry->default_vehicle];
4162
4163 if (vehicle_entry->flags & VEHICLE_ENTRY_FLAG_GO_KART)
4164 {
4165 shiftMultiplier = 9;
4166 }
4167
4168 if (vehicle_entry->flags & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
4169 {
4170 specialDirection = ((vehicle->sprite_direction + 3) / 8) + 1;
4171 specialDirection &= 3;
4172
4173 if (vehicle->TrackSubposition == VehicleTrackSubposition::GoKartsRightLane)
4174 specialDirection = direction_reverse(specialDirection);
4175 }
4176 }
4177
4178 int16_t xShift = DirectionOffsets[specialDirection].x;
4179 int16_t yShift = DirectionOffsets[specialDirection].y;
4180
4181 platformLocation.x = vehicle->x + xShift * shiftMultiplier;
4182 platformLocation.y = vehicle->y + yShift * shiftMultiplier;
4183
4184 peep_go_to_ride_exit(
4185 this, ride, platformLocation.x, platformLocation.y, platformLocation.z, platformLocation.direction);
4186 return;
4187 }
4188
4189 platformLocation.x = vehicle->x + DirectionOffsets[platformLocation.direction].x * 12;
4190 platformLocation.y = vehicle->y + DirectionOffsets[platformLocation.direction].y * 12;
4191
4192 // This can evaluate to false with buggy custom rides.
4193 if (CurrentSeat < vehicle_entry->peep_loading_positions.size())
4194 {
4195 int8_t loadPosition = vehicle_entry->peep_loading_positions[CurrentSeat];
4196
4197 switch (vehicle->sprite_direction / 8)
4198 {
4199 case 0:
4200 platformLocation.x -= loadPosition;
4201 break;
4202 case 1:
4203 platformLocation.y += loadPosition;
4204 break;
4205 case 2:
4206 platformLocation.x += loadPosition;
4207 break;
4208 case 3:
4209 platformLocation.y -= loadPosition;
4210 break;
4211 }
4212 }
4213 else
4214 {
4215 log_verbose(
4216 "CurrentSeat %d is too large! (Vehicle entry has room for %d.)", CurrentSeat,
4217 vehicle_entry->peep_loading_positions.size());
4218 }
4219
4220 platformLocation.z = ride->stations[CurrentRideStation].GetBaseZ();
4221
4222 peep_go_to_ride_exit(
4223 this, ride, platformLocation.x, platformLocation.y, platformLocation.z, platformLocation.direction);
4224 return;
4225 }
4226
4227 auto exitLocation = ride_get_exit_location(ride, CurrentRideStation).ToCoordsXYZD();
4228 Guard::Assert(!exitLocation.IsNull());
4229
4230 auto waypointLoc = CoordsXYZ{ ride->stations[CurrentRideStation].Start.ToTileCentre(),
4231 exitLocation.z + ride->GetRideTypeDescriptor().Heights.PlatformHeight };
4232
4233 TileElement* trackElement = ride_get_station_start_track_element(ride, CurrentRideStation);
4234
4235 Direction station_direction = (trackElement == nullptr ? 0 : trackElement->GetDirection());
4236
4237 vehicle = GetEntity<Vehicle>(ride->vehicles[CurrentTrain]);
4238 if (vehicle == nullptr)
4239 {
4240 return;
4241 }
4242
4243 rideEntry = vehicle->GetRideEntry();
4244 rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle->vehicle_type];
4245 if (vehicleEntry == nullptr)
4246 return;
4247
4248 Var37 = ((exitLocation.direction | GetWaypointedSeatLocation(*ride, vehicleEntry, station_direction) * 4) * 4) | 1;
4249
4250 if (ride->type == RIDE_TYPE_ENTERPRISE)
4251 {
4252 waypointLoc.x = vehicle->x;
4253 waypointLoc.y = vehicle->y;
4254 }
4255
4256 Guard::Assert(vehicleEntry->peep_loading_waypoints.size() >= static_cast<size_t>(Var37 / 4));
4257 CoordsXYZ exitWaypointLoc = waypointLoc;
4258
4259 exitWaypointLoc.x += vehicleEntry->peep_loading_waypoints[Var37 / 4][2].x;
4260 exitWaypointLoc.y += vehicleEntry->peep_loading_waypoints[Var37 / 4][2].y;
4261
4262 if (ride->type == RIDE_TYPE_MOTION_SIMULATOR)
4263 exitWaypointLoc.z += 15;
4264
4265 MoveTo(exitWaypointLoc);
4266
4267 waypointLoc.x += vehicleEntry->peep_loading_waypoints[Var37 / 4][1].x;
4268 waypointLoc.y += vehicleEntry->peep_loading_waypoints[Var37 / 4][1].y;
4269
4270 SetDestination(waypointLoc, 2);
4271 RideSubState = PeepRideSubState::ApproachExitWaypoints;
4272 }
4273
4274 /**
4275 *
4276 * rct2: 0x0069376A
4277 */
UpdateRidePrepareForExit()4278 void Guest::UpdateRidePrepareForExit()
4279 {
4280 auto ride = get_ride(CurrentRide);
4281 if (ride == nullptr || CurrentRideStation >= std::size(ride->stations))
4282 return;
4283
4284 auto exit = ride_get_exit_location(ride, CurrentRideStation);
4285
4286 auto newDestination = exit.ToCoordsXY().ToTileCentre();
4287
4288 auto xShift = DirectionOffsets[exit.direction].x;
4289 auto yShift = DirectionOffsets[exit.direction].y;
4290
4291 int16_t shiftMultiplier = 20;
4292
4293 rct_ride_entry* rideEntry = ride->GetRideEntry();
4294 if (rideEntry != nullptr)
4295 {
4296 rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[rideEntry->default_vehicle];
4297 if (vehicleEntry->flags & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
4298 {
4299 shiftMultiplier = 32;
4300 }
4301 }
4302
4303 xShift *= shiftMultiplier;
4304 yShift *= shiftMultiplier;
4305
4306 newDestination.x -= xShift;
4307 newDestination.y -= yShift;
4308
4309 SetDestination(newDestination, 2);
4310 RideSubState = PeepRideSubState::InExit;
4311 }
4312
4313 /**
4314 *
4315 * rct2: 0x0069374F
4316 */
UpdateRideApproachExit()4317 void Guest::UpdateRideApproachExit()
4318 {
4319 if (auto loc = UpdateAction(); loc.has_value())
4320 {
4321 MoveTo({ loc.value(), z });
4322 return;
4323 }
4324
4325 UpdateRidePrepareForExit();
4326 }
4327
4328 /**
4329 *
4330 * rct2: 0x0069382E
4331 */
UpdateRideInExit()4332 void Guest::UpdateRideInExit()
4333 {
4334 auto ride = get_ride(CurrentRide);
4335 if (ride == nullptr)
4336 return;
4337
4338 int16_t xy_distance;
4339
4340 if (auto loc = UpdateAction(xy_distance); loc.has_value())
4341 {
4342 if (xy_distance >= 16)
4343 {
4344 int16_t actionZ = ride->stations[CurrentRideStation].GetBaseZ();
4345
4346 actionZ += ride->GetRideTypeDescriptor().Heights.PlatformHeight;
4347 MoveTo({ loc.value(), actionZ });
4348 return;
4349 }
4350
4351 SwitchToSpecialSprite(0);
4352 MoveTo({ loc.value(), z });
4353 }
4354
4355 if (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO)
4356 {
4357 ShopItem secondaryItem = ride->GetRideTypeDescriptor().PhotoItem;
4358 if (DecideAndBuyItem(ride, secondaryItem, ride->price[1]))
4359 {
4360 ride->no_secondary_items_sold++;
4361 }
4362 }
4363 RideSubState = PeepRideSubState::LeaveExit;
4364 }
4365 #pragma warning(default : 6011)
4366 /**
4367 *
4368 * rct2: 0x006926AD
4369 */
UpdateRideApproachVehicleWaypoints()4370 void Guest::UpdateRideApproachVehicleWaypoints()
4371 {
4372 auto ride = get_ride(CurrentRide);
4373 if (ride == nullptr)
4374 return;
4375
4376 int16_t xy_distance;
4377 uint8_t waypoint = Var37 & 3;
4378
4379 if (auto loc = UpdateAction(xy_distance); loc.has_value())
4380 {
4381 int16_t actionZ;
4382 // Motion simulators have steps this moves the peeps up the steps
4383 if (ride->type == RIDE_TYPE_MOTION_SIMULATOR)
4384 {
4385 actionZ = ride->stations[CurrentRideStation].GetBaseZ() + 2;
4386
4387 if (waypoint == 2)
4388 {
4389 xy_distance -= 12;
4390 if (xy_distance < 0)
4391 xy_distance = 0;
4392
4393 if (xy_distance <= 15)
4394 {
4395 actionZ += 15 - xy_distance;
4396 }
4397 }
4398 }
4399 else
4400 {
4401 actionZ = z;
4402 }
4403 MoveTo({ loc.value(), actionZ });
4404 return;
4405 }
4406
4407 if (waypoint == 2)
4408 {
4409 RideSubState = PeepRideSubState::EnterVehicle;
4410 return;
4411 }
4412
4413 waypoint++;
4414 // This is incrementing the actual peep waypoint
4415 Var37++;
4416
4417 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[CurrentTrain]);
4418 if (vehicle == nullptr)
4419 {
4420 return;
4421 }
4422
4423 CoordsXY targetLoc = ride->stations[CurrentRideStation].Start.ToTileCentre();
4424
4425 if (ride->type == RIDE_TYPE_ENTERPRISE)
4426 {
4427 targetLoc.x = vehicle->x;
4428 targetLoc.y = vehicle->y;
4429 }
4430
4431 rct_ride_entry* ride_entry = vehicle->GetRideEntry();
4432 if (ride_entry == nullptr)
4433 {
4434 return;
4435 }
4436
4437 rct_ride_entry_vehicle* vehicle_type = &ride_entry->vehicles[vehicle->vehicle_type];
4438 Guard::Assert(waypoint < 3);
4439 targetLoc.x += vehicle_type->peep_loading_waypoints[Var37 / 4][waypoint].x;
4440 targetLoc.y += vehicle_type->peep_loading_waypoints[Var37 / 4][waypoint].y;
4441
4442 SetDestination(targetLoc);
4443 }
4444
4445 /**
4446 *
4447 * rct2: 0x0069357D
4448 */
UpdateRideApproachExitWaypoints()4449 void Guest::UpdateRideApproachExitWaypoints()
4450 {
4451 auto ride = get_ride(CurrentRide);
4452 if (ride == nullptr)
4453 return;
4454
4455 int16_t xy_distance;
4456
4457 if (auto loc = UpdateAction(xy_distance); loc.has_value())
4458 {
4459 int16_t actionZ;
4460 if (ride->type == RIDE_TYPE_MOTION_SIMULATOR)
4461 {
4462 actionZ = ride->stations[CurrentRideStation].GetBaseZ() + 2;
4463
4464 if ((Var37 & 3) == 1)
4465 {
4466 if (xy_distance > 15)
4467 xy_distance = 15;
4468
4469 actionZ += xy_distance;
4470 }
4471 }
4472 else
4473 {
4474 actionZ = z;
4475 }
4476 MoveTo({ loc.value(), actionZ });
4477 return;
4478 }
4479
4480 if ((Var37 & 3) != 0)
4481 {
4482 if ((Var37 & 3) == 3)
4483 {
4484 UpdateRidePrepareForExit();
4485 return;
4486 }
4487
4488 Var37--;
4489 Vehicle* vehicle = GetEntity<Vehicle>(ride->vehicles[CurrentTrain]);
4490 if (vehicle == nullptr)
4491 {
4492 return;
4493 }
4494 CoordsXY targetLoc = ride->stations[CurrentRideStation].Start.ToTileCentre();
4495
4496 if (ride->type == RIDE_TYPE_ENTERPRISE)
4497 {
4498 targetLoc.x = vehicle->x;
4499 targetLoc.y = vehicle->y;
4500 }
4501
4502 rct_ride_entry* rideEntry = vehicle->GetRideEntry();
4503 rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle->vehicle_type];
4504
4505 Guard::Assert((Var37 & 3) < 3);
4506 targetLoc.x += vehicleEntry->peep_loading_waypoints[Var37 / 4][Var37 & 3].x;
4507 targetLoc.y += vehicleEntry->peep_loading_waypoints[Var37 / 4][Var37 & 3].y;
4508
4509 SetDestination(targetLoc);
4510 return;
4511 }
4512
4513 Var37 |= 3;
4514
4515 auto targetLoc = ride_get_exit_location(ride, CurrentRideStation).ToCoordsXYZD().ToTileCentre();
4516 uint8_t exit_direction = direction_reverse(targetLoc.direction);
4517
4518 int16_t x_shift = DirectionOffsets[exit_direction].x;
4519 int16_t y_shift = DirectionOffsets[exit_direction].y;
4520
4521 int16_t shift_multiplier = 20;
4522
4523 auto rideEntry = get_ride_entry(ride->subtype);
4524 if (rideEntry != nullptr)
4525 {
4526 auto vehicleEntry = &rideEntry->vehicles[rideEntry->default_vehicle];
4527 if (vehicleEntry->flags & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
4528 {
4529 shift_multiplier = 32;
4530 }
4531 }
4532
4533 x_shift *= shift_multiplier;
4534 y_shift *= shift_multiplier;
4535
4536 targetLoc.x -= x_shift;
4537 targetLoc.y -= y_shift;
4538
4539 SetDestination(targetLoc);
4540 }
4541
4542 /**
4543 *
4544 * rct2: 0x006927B3
4545 */
UpdateRideApproachSpiralSlide()4546 void Guest::UpdateRideApproachSpiralSlide()
4547 {
4548 auto ride = get_ride(CurrentRide);
4549 if (ride == nullptr)
4550 return;
4551
4552 if (auto loc = UpdateAction(); loc.has_value())
4553 {
4554 MoveTo({ loc.value(), z });
4555 return;
4556 }
4557
4558 uint8_t waypoint = Var37 & 3;
4559
4560 if (waypoint == 3)
4561 {
4562 SubState = 15;
4563 SetDestination({ 0, 0 });
4564 Var37 = (Var37 / 4) & 0xC;
4565 MoveTo({ LOCATION_NULL, y, z });
4566 return;
4567 }
4568
4569 if (waypoint == 2)
4570 {
4571 bool lastRide = false;
4572 if (ride->status != RideStatus::Open)
4573 lastRide = true;
4574 else if (CurrentCar++ != 0)
4575 {
4576 if (ride->mode == RideMode::SingleRidePerAdmission)
4577 lastRide = true;
4578 if (static_cast<uint8_t>(CurrentCar - 1) > (scenario_rand() & 0xF))
4579 lastRide = true;
4580 }
4581
4582 if (lastRide)
4583 {
4584 auto exit = ride_get_exit_location(ride, CurrentRideStation);
4585 waypoint = 1;
4586 Var37 = (exit.direction * 4) | (Var37 & 0x30) | waypoint;
4587 CoordsXY targetLoc = ride->stations[CurrentRideStation].Start;
4588
4589 assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
4590 targetLoc += SpiralSlideWalkingPath[Var37];
4591
4592 SetDestination(targetLoc);
4593 RideSubState = PeepRideSubState::LeaveSpiralSlide;
4594 return;
4595 }
4596 }
4597 waypoint++;
4598 // Actually increment the real peep waypoint
4599 Var37++;
4600
4601 CoordsXY targetLoc = ride->stations[CurrentRideStation].Start;
4602
4603 assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
4604 targetLoc += SpiralSlideWalkingPath[Var37];
4605
4606 SetDestination(targetLoc);
4607 }
4608
4609 /** rct2: 0x00981F0C, 0x00981F0E */
4610 static constexpr const CoordsXY _SpiralSlideEnd[] = {
4611 { 25, 56 },
4612 { 56, 7 },
4613 { 7, -24 },
4614 { -24, 25 },
4615 };
4616
4617 /** rct2: 0x00981F1C, 0x00981F1E */
4618 static constexpr const CoordsXY _SpiralSlideEndWaypoint[] = {
4619 { 8, 56 },
4620 { 56, 24 },
4621 { 24, -24 },
4622 { -24, 8 },
4623 };
4624
4625 /**
4626 *
4627 * rct2: 0x00692D83
4628 */
UpdateRideOnSpiralSlide()4629 void Guest::UpdateRideOnSpiralSlide()
4630 {
4631 auto ride = get_ride(CurrentRide);
4632 if (ride == nullptr || ride->type != RIDE_TYPE_SPIRAL_SLIDE)
4633 return;
4634
4635 auto destination = GetDestination();
4636 if ((Var37 & 3) == 0)
4637 {
4638 switch (destination.x)
4639 {
4640 case 0:
4641 destination.y++;
4642 if (destination.y >= 30)
4643 destination.x++;
4644
4645 SetDestination(destination);
4646 return;
4647 case 1:
4648 if (ride->slide_in_use != 0)
4649 return;
4650
4651 ride->slide_in_use++;
4652 ride->slide_peep = sprite_index;
4653 ride->slide_peep_t_shirt_colour = TshirtColour;
4654 ride->spiral_slide_progress = 0;
4655 destination.x++;
4656
4657 SetDestination(destination);
4658 return;
4659 case 2:
4660 return;
4661 case 3:
4662 {
4663 auto newLocation = ride->stations[CurrentRideStation].Start;
4664 uint8_t dir = (Var37 / 4) & 3;
4665
4666 // Set the location that the peep walks to go on slide again
4667 destination = newLocation + _SpiralSlideEndWaypoint[dir];
4668 SetDestination(destination);
4669
4670 // Move the peep sprite to just at the end of the slide
4671 newLocation.x += _SpiralSlideEnd[dir].x;
4672 newLocation.y += _SpiralSlideEnd[dir].y;
4673
4674 MoveTo({ newLocation, z });
4675
4676 sprite_direction = (Var37 & 0xC) * 2;
4677
4678 Var37++;
4679 return;
4680 }
4681 default:
4682 return;
4683 }
4684 }
4685
4686 if (auto loc = UpdateAction(); loc.has_value())
4687 {
4688 MoveTo({ loc.value(), z });
4689 return;
4690 }
4691
4692 uint8_t waypoint = 2;
4693 Var37 = (Var37 * 4 & 0x30) + waypoint;
4694
4695 CoordsXY targetLoc = ride->stations[CurrentRideStation].Start;
4696
4697 assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
4698 targetLoc += SpiralSlideWalkingPath[Var37];
4699
4700 SetDestination(targetLoc);
4701 RideSubState = PeepRideSubState::ApproachSpiralSlide;
4702 }
4703
4704 /**
4705 *
4706 * rct2: 0x00692C6B
4707 */
UpdateRideLeaveSpiralSlide()4708 void Guest::UpdateRideLeaveSpiralSlide()
4709 {
4710 // Iterates through the spiral slide waypoints until it reaches
4711 // waypoint 0. Then it readies to leave the ride by the entrance.
4712 if (auto loc = UpdateAction(); loc.has_value())
4713 {
4714 MoveTo({ loc.value(), z });
4715 return;
4716 }
4717
4718 auto ride = get_ride(CurrentRide);
4719 if (ride == nullptr)
4720 return;
4721
4722 uint8_t waypoint = Var37 & 3;
4723
4724 if (waypoint != 0)
4725 {
4726 if (waypoint == 3)
4727 {
4728 UpdateRidePrepareForExit();
4729 return;
4730 }
4731
4732 waypoint--;
4733 // Actually decrement the peep waypoint
4734 Var37--;
4735 CoordsXY targetLoc = ride->stations[CurrentRideStation].Start;
4736
4737 assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
4738 targetLoc += SpiralSlideWalkingPath[Var37];
4739
4740 SetDestination(targetLoc);
4741 return;
4742 }
4743
4744 // Actually force the final waypoint
4745 Var37 |= 3;
4746
4747 auto targetLoc = ride_get_exit_location(ride, CurrentRideStation).ToCoordsXYZD().ToTileCentre();
4748
4749 int16_t xShift = DirectionOffsets[direction_reverse(targetLoc.direction)].x;
4750 int16_t yShift = DirectionOffsets[direction_reverse(targetLoc.direction)].y;
4751
4752 int16_t shiftMultiplier = 20;
4753
4754 xShift *= shiftMultiplier;
4755 yShift *= shiftMultiplier;
4756
4757 targetLoc.x -= xShift;
4758 targetLoc.y -= yShift;
4759
4760 SetDestination(targetLoc);
4761 }
4762
4763 /** rct2: 0x00981FE4 */
4764 static constexpr const uint8_t _MazeGetNewDirectionFromEdge[][4] = {
4765 { 15, 7, 15, 7 },
4766 { 11, 3, 11, 3 },
4767 { 7, 15, 7, 15 },
4768 { 3, 11, 3, 11 },
4769 };
4770
4771 /** rct2: 0x00981FF4 */
4772 static constexpr const uint8_t _MazeCurrentDirectionToOpenHedge[][4] = {
4773 { 1, 2, 14, 0 },
4774 { 4, 5, 6, 2 },
4775 { 6, 8, 9, 10 },
4776 { 14, 10, 12, 13 },
4777 };
4778
4779 /**
4780 *
4781 * rct2: 0x00692A83
4782 */
UpdateRideMazePathfinding()4783 void Guest::UpdateRideMazePathfinding()
4784 {
4785 if (auto loc = UpdateAction(); loc.has_value())
4786 {
4787 MoveTo({ loc.value(), z });
4788 return;
4789 }
4790
4791 auto ride = get_ride(CurrentRide);
4792 if (ride == nullptr)
4793 return;
4794
4795 if (Var37 == 16)
4796 {
4797 UpdateRidePrepareForExit();
4798 return;
4799 }
4800
4801 if (IsActionInterruptable())
4802 {
4803 if (Energy > 64 && (scenario_rand() & 0xFFFF) <= 2427)
4804 {
4805 Action = PeepActionType::Jump;
4806 ActionFrame = 0;
4807 ActionSpriteImageOffset = 0;
4808 UpdateCurrentActionSpriteType();
4809 }
4810 }
4811
4812 auto targetLoc = GetDestination().ToTileStart();
4813
4814 int16_t stationBaseZ = ride->stations[0].GetBaseZ();
4815
4816 // Find the station track element
4817 auto trackElement = map_get_track_element_at({ targetLoc, stationBaseZ });
4818 if (trackElement == nullptr)
4819 {
4820 return;
4821 }
4822
4823 uint16_t mazeEntry = trackElement->GetMazeEntry();
4824 // Var37 is 3, 7, 11 or 15
4825 uint8_t hedges[4]{ 0xFF, 0xFF, 0xFF, 0xFF };
4826 uint8_t openCount = 0;
4827 uint8_t mazeReverseLastEdge = direction_reverse(MazeLastEdge);
4828 for (uint8_t i = 0; i < 4; ++i)
4829 {
4830 if (!(mazeEntry & (1 << _MazeCurrentDirectionToOpenHedge[Var37 / 4][i])) && i != mazeReverseLastEdge)
4831 {
4832 hedges[openCount++] = i;
4833 }
4834 }
4835
4836 if (openCount == 0)
4837 {
4838 if ((mazeEntry & (1 << _MazeCurrentDirectionToOpenHedge[Var37 / 4][mazeReverseLastEdge])))
4839 {
4840 return;
4841 }
4842 hedges[openCount++] = mazeReverseLastEdge;
4843 }
4844
4845 uint8_t chosenEdge = hedges[scenario_rand() % openCount];
4846 assert(chosenEdge != 0xFF);
4847
4848 targetLoc = GetDestination() + CoordsDirectionDelta[chosenEdge] / 2;
4849
4850 enum class maze_type
4851 {
4852 invalid,
4853 hedge,
4854 entrance_or_exit
4855 };
4856 maze_type mazeType = maze_type::invalid;
4857
4858 auto tileElement = map_get_first_element_at(targetLoc);
4859 if (tileElement == nullptr)
4860 return;
4861 do
4862 {
4863 if (stationBaseZ != tileElement->GetBaseZ())
4864 continue;
4865
4866 if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
4867 {
4868 mazeType = maze_type::hedge;
4869 break;
4870 }
4871
4872 if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE
4873 && tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_RIDE_EXIT)
4874 {
4875 mazeType = maze_type::entrance_or_exit;
4876 break;
4877 }
4878 } while (!(tileElement++)->IsLastForTile());
4879
4880 switch (mazeType)
4881 {
4882 case maze_type::invalid:
4883 MazeLastEdge++;
4884 MazeLastEdge &= 3;
4885 return;
4886 case maze_type::hedge:
4887 SetDestination(targetLoc);
4888 Var37 = _MazeGetNewDirectionFromEdge[Var37 / 4][chosenEdge];
4889 MazeLastEdge = chosenEdge;
4890 break;
4891 case maze_type::entrance_or_exit:
4892 targetLoc = GetDestination();
4893 if (chosenEdge & 1)
4894 {
4895 targetLoc.x = targetLoc.ToTileCentre().x;
4896 }
4897 else
4898 {
4899 targetLoc.y = targetLoc.ToTileCentre().y;
4900 }
4901 SetDestination(targetLoc);
4902 Var37 = 16;
4903 MazeLastEdge = chosenEdge;
4904 break;
4905 }
4906
4907 if (auto loc = UpdateAction(); loc.has_value())
4908 {
4909 MoveTo({ loc.value(), z });
4910 return;
4911 }
4912 }
4913
4914 /**
4915 *
4916 * rct2: 0x006938D2
4917 */
UpdateRideLeaveExit()4918 void Guest::UpdateRideLeaveExit()
4919 {
4920 auto ride = get_ride(CurrentRide);
4921
4922 if (auto loc = UpdateAction(); loc.has_value())
4923 {
4924 if (ride != nullptr)
4925 {
4926 MoveTo({ loc.value(), ride->stations[CurrentRideStation].GetBaseZ() });
4927 }
4928 return;
4929 }
4930
4931 OnExitRide(ride);
4932
4933 if (ride != nullptr && (PeepFlags & PEEP_FLAGS_TRACKING))
4934 {
4935 auto ft = Formatter();
4936 FormatNameTo(ft);
4937 ride->FormatNameTo(ft);
4938
4939 if (gConfigNotifications.guest_left_ride)
4940 {
4941 News::AddItemToQueue(News::ItemType::PeepOnRide, STR_PEEP_TRACKING_LEFT_RIDE_X, sprite_index, ft);
4942 }
4943 }
4944
4945 InteractionRideIndex = RIDE_ID_NULL;
4946 SetState(PeepState::Falling);
4947
4948 CoordsXY targetLoc = { x, y };
4949
4950 // Find the station track element
4951 for (auto* pathElement : TileElementsView<PathElement>(targetLoc))
4952 {
4953 int16_t height = map_height_from_slope(targetLoc, pathElement->GetSlopeDirection(), pathElement->IsSloped());
4954 height += pathElement->GetBaseZ();
4955
4956 int16_t z_diff = z - height;
4957 if (z_diff > 0 || z_diff < -16)
4958 continue;
4959
4960 MoveTo({ x, y, height });
4961 return;
4962 }
4963 }
4964
4965 /**
4966 *
4967 * rct2: 0x0069299C
4968 */
UpdateRideShopApproach()4969 void Guest::UpdateRideShopApproach()
4970 {
4971 if (auto loc = UpdateAction(); loc.has_value())
4972 {
4973 MoveTo({ loc.value(), z });
4974 return;
4975 }
4976
4977 RideSubState = PeepRideSubState::InteractShop;
4978 }
4979
4980 /**
4981 *
4982 * rct2: 0x006929BB
4983 */
UpdateRideShopInteract()4984 void Guest::UpdateRideShopInteract()
4985 {
4986 auto ride = get_ride(CurrentRide);
4987 if (ride == nullptr)
4988 return;
4989
4990 const int16_t tileCentreX = NextLoc.x + 16;
4991 const int16_t tileCentreY = NextLoc.y + 16;
4992 if (ride->type == RIDE_TYPE_FIRST_AID)
4993 {
4994 if (Nausea <= 35)
4995 {
4996 RideSubState = PeepRideSubState::LeaveShop;
4997
4998 SetDestination({ tileCentreX, tileCentreY }, 3);
4999 HappinessTarget = std::min(HappinessTarget + 30, PEEP_MAX_HAPPINESS);
5000 Happiness = HappinessTarget;
5001 }
5002 else
5003 {
5004 Nausea--;
5005 NauseaTarget = Nausea;
5006 }
5007 return;
5008 }
5009
5010 if (Toilet != 0)
5011 {
5012 Toilet--;
5013 return;
5014 }
5015
5016 // Do not play toilet flush sound on title screen as it's considered loud and annoying
5017 if (!(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO))
5018 {
5019 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::ToiletFlush, GetLocation());
5020 }
5021
5022 RideSubState = PeepRideSubState::LeaveShop;
5023
5024 SetDestination({ tileCentreX, tileCentreY }, 3);
5025
5026 HappinessTarget = std::min(HappinessTarget + 30, PEEP_MAX_HAPPINESS);
5027 Happiness = HappinessTarget;
5028 StopPurchaseThought(ride->type);
5029 }
5030
5031 /**
5032 *
5033 * rct2: 0x00692935
5034 */
UpdateRideShopLeave()5035 void Guest::UpdateRideShopLeave()
5036 {
5037 if (auto loc = UpdateAction(); loc.has_value())
5038 {
5039 const auto curLoc = GetLocation();
5040 MoveTo({ loc.value(), curLoc.z });
5041
5042 const auto newLoc = GetLocation().ToTileStart();
5043 if (newLoc.x != NextLoc.x)
5044 return;
5045 if (newLoc.y != NextLoc.y)
5046 return;
5047 }
5048
5049 //#11758 Previously SetState(PeepState::Walking) caused Peeps to double-back to exit point of shop
5050 SetState(PeepState::Falling);
5051
5052 auto ride = get_ride(CurrentRide);
5053 if (ride != nullptr)
5054 {
5055 ride->total_customers++;
5056 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;
5057 ride_update_satisfaction(ride, Happiness / 64);
5058 }
5059 }
5060
UpdateGuest()5061 void Guest::UpdateGuest()
5062 {
5063 switch (State)
5064 {
5065 case PeepState::QueuingFront:
5066 UpdateRide();
5067 break;
5068 case PeepState::LeavingRide:
5069 UpdateRide();
5070 break;
5071 case PeepState::Walking:
5072 UpdateWalking();
5073 break;
5074 case PeepState::Queuing:
5075 UpdateQueuing();
5076 break;
5077 case PeepState::EnteringRide:
5078 UpdateRide();
5079 break;
5080 case PeepState::Sitting:
5081 UpdateSitting();
5082 break;
5083 case PeepState::EnteringPark:
5084 UpdateEnteringPark();
5085 break;
5086 case PeepState::LeavingPark:
5087 UpdateLeavingPark();
5088 break;
5089 case PeepState::Buying:
5090 UpdateBuying();
5091 break;
5092 case PeepState::Watching:
5093 UpdateWatching();
5094 break;
5095 case PeepState::UsingBin:
5096 UpdateUsingBin();
5097 break;
5098 default:
5099 // TODO reset to default state
5100 assert(false);
5101 break;
5102 }
5103 }
5104
5105 /**
5106 *
5107 * rct2: 0x691A30
5108 * Used by entering_ride and queueing_front */
UpdateRide()5109 void Guest::UpdateRide()
5110 {
5111 NextFlags &= ~PEEP_NEXT_FLAG_IS_SLOPED;
5112
5113 switch (RideSubState)
5114 {
5115 case PeepRideSubState::AtEntrance:
5116 UpdateRideAtEntrance();
5117 break;
5118 case PeepRideSubState::InEntrance:
5119 UpdateRideAdvanceThroughEntrance();
5120 break;
5121 case PeepRideSubState::FreeVehicleCheck:
5122 UpdateRideFreeVehicleCheck();
5123 break;
5124 case PeepRideSubState::LeaveEntrance:
5125 UpdateRideAdvanceThroughEntrance();
5126 break;
5127 case PeepRideSubState::ApproachVehicle:
5128 UpdateRideApproachVehicle();
5129 break;
5130 case PeepRideSubState::EnterVehicle:
5131 UpdateRideEnterVehicle();
5132 break;
5133 case PeepRideSubState::OnRide:
5134 // No action, on ride.
5135 break;
5136 case PeepRideSubState::LeaveVehicle:
5137 UpdateRideLeaveVehicle();
5138 break;
5139 case PeepRideSubState::ApproachExit:
5140 UpdateRideApproachExit();
5141 break;
5142 case PeepRideSubState::InExit:
5143 UpdateRideInExit();
5144 break;
5145 case PeepRideSubState::ApproachVehicleWaypoints:
5146 UpdateRideApproachVehicleWaypoints();
5147 break;
5148 case PeepRideSubState::ApproachExitWaypoints:
5149 UpdateRideApproachExitWaypoints();
5150 break;
5151 case PeepRideSubState::ApproachSpiralSlide:
5152 UpdateRideApproachSpiralSlide();
5153 break;
5154 case PeepRideSubState::OnSpiralSlide:
5155 UpdateRideOnSpiralSlide();
5156 break;
5157 case PeepRideSubState::LeaveSpiralSlide:
5158 UpdateRideLeaveSpiralSlide();
5159 break;
5160 case PeepRideSubState::MazePathfinding:
5161 UpdateRideMazePathfinding();
5162 break;
5163 case PeepRideSubState::LeaveExit:
5164 UpdateRideLeaveExit();
5165 break;
5166 case PeepRideSubState::ApproachShop:
5167 UpdateRideShopApproach();
5168 break;
5169 case PeepRideSubState::InteractShop:
5170 UpdateRideShopInteract();
5171 break;
5172 case PeepRideSubState::LeaveShop:
5173 UpdateRideShopLeave();
5174 break;
5175 default:
5176 // Invalid peep sub-state
5177 assert(false);
5178 break;
5179 }
5180 }
5181
5182 static void peep_update_walking_break_scenery(Guest* peep);
5183 static bool peep_find_ride_to_look_at(Peep* peep, uint8_t edge, ride_id_t* rideToView, uint8_t* rideSeatToView);
5184
5185 /**
5186 *
5187 * rct2: 0x0069030A
5188 */
UpdateWalking()5189 void Guest::UpdateWalking()
5190 {
5191 if (!CheckForPath())
5192 return;
5193
5194 if (PeepFlags & PEEP_FLAGS_WAVING)
5195 {
5196 if (IsActionInterruptable())
5197 {
5198 if ((0xFFFF & scenario_rand()) < 936)
5199 {
5200 Action = PeepActionType::Wave2;
5201 ActionFrame = 0;
5202 ActionSpriteImageOffset = 0;
5203
5204 UpdateCurrentActionSpriteType();
5205 }
5206 }
5207 }
5208
5209 if (PeepFlags & PEEP_FLAGS_PHOTO)
5210 {
5211 if (IsActionInterruptable())
5212 {
5213 if ((0xFFFF & scenario_rand()) < 936)
5214 {
5215 Action = PeepActionType::TakePhoto;
5216 ActionFrame = 0;
5217 ActionSpriteImageOffset = 0;
5218
5219 UpdateCurrentActionSpriteType();
5220 }
5221 }
5222 }
5223
5224 if (PeepFlags & PEEP_FLAGS_PAINTING)
5225 {
5226 if (IsActionInterruptable())
5227 {
5228 if ((0xFFFF & scenario_rand()) < 936)
5229 {
5230 Action = PeepActionType::DrawPicture;
5231 ActionFrame = 0;
5232 ActionSpriteImageOffset = 0;
5233
5234 UpdateCurrentActionSpriteType();
5235 }
5236 }
5237 }
5238
5239 if (PeepFlags & PEEP_FLAGS_LITTER)
5240 {
5241 if (!GetNextIsSurface())
5242 {
5243 if ((0xFFFF & scenario_rand()) <= 4096)
5244 {
5245 static constexpr const Litter::Type litter_types[] = {
5246 Litter::Type::EmptyCan,
5247 Litter::Type::Rubbish,
5248 Litter::Type::BurgerBox,
5249 Litter::Type::EmptyCup,
5250 };
5251 auto litterType = litter_types[scenario_rand() & 0x3];
5252 const auto loc = GetLocation();
5253 int32_t litterX = loc.x + (scenario_rand() & 0x7) - 3;
5254 int32_t litterY = loc.y + (scenario_rand() & 0x7) - 3;
5255 Direction litterDirection = (scenario_rand() & 0x3);
5256
5257 Litter::Create({ litterX, litterY, loc.z, litterDirection }, litterType);
5258 }
5259 }
5260 }
5261 else if (HasEmptyContainer())
5262 {
5263 if ((!GetNextIsSurface()) && (static_cast<uint32_t>(sprite_index & 0x1FF) == (gCurrentTicks & 0x1FF))
5264 && ((0xFFFF & scenario_rand()) <= 4096))
5265 {
5266 int32_t container = bitscanforward(GetEmptyContainerFlags());
5267 auto litterType = Litter::Type::Vomit;
5268
5269 if (container != -1)
5270 {
5271 auto item = static_cast<ShopItem>(container);
5272 RemoveItem(item);
5273 litterType = Litter::Type(GetShopItemDescriptor(item).Type);
5274 }
5275
5276 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_INVENTORY;
5277 UpdateSpriteType();
5278
5279 const auto loc = GetLocation();
5280 int32_t litterX = loc.x + (scenario_rand() & 0x7) - 3;
5281 int32_t litterY = loc.y + (scenario_rand() & 0x7) - 3;
5282 Direction litterDirection = (scenario_rand() & 0x3);
5283
5284 Litter::Create({ litterX, litterY, loc.z, litterDirection }, litterType);
5285 }
5286 }
5287
5288 // Check if vehicle is blocking the destination tile
5289 auto curPos = TileCoordsXYZ(GetLocation());
5290 auto dstPos = TileCoordsXYZ(CoordsXYZ{ GetDestination(), NextLoc.z });
5291 if (curPos.x != dstPos.x || curPos.y != dstPos.y)
5292 {
5293 if (footpath_is_blocked_by_vehicle(dstPos))
5294 {
5295 // Wait for vehicle to pass
5296 return;
5297 }
5298 }
5299
5300 uint8_t pathingResult;
5301 PerformNextAction(pathingResult);
5302 if (!(pathingResult & PATHING_DESTINATION_REACHED))
5303 return;
5304
5305 if (GetNextIsSurface())
5306 {
5307 auto surfaceElement = map_get_surface_element_at(NextLoc);
5308
5309 if (surfaceElement != nullptr)
5310 {
5311 int32_t water_height = surfaceElement->GetWaterHeight();
5312 if (water_height > 0)
5313 {
5314 MoveTo({ x, y, water_height });
5315 SetState(PeepState::Falling);
5316 return;
5317 }
5318 }
5319 }
5320
5321 CheckIfLost();
5322 CheckCantFindRide();
5323 CheckCantFindExit();
5324
5325 if (UpdateWalkingFindBench())
5326 return;
5327
5328 if (UpdateWalkingFindBin())
5329 return;
5330
5331 peep_update_walking_break_scenery(this);
5332
5333 if (State != PeepState::Walking)
5334 return;
5335
5336 if (PeepFlags & PEEP_FLAGS_LEAVING_PARK)
5337 return;
5338
5339 if (Nausea > 140)
5340 return;
5341
5342 if (Happiness < 120)
5343 return;
5344
5345 if (Toilet > 140)
5346 return;
5347
5348 uint16_t chance = HasFoodOrDrink() ? 13107 : 2849;
5349
5350 if ((scenario_rand() & 0xFFFF) > chance)
5351 return;
5352
5353 if (GetNextIsSurface() || GetNextIsSloped())
5354 return;
5355
5356 TileElement* tileElement = map_get_first_element_at(NextLoc);
5357 if (tileElement == nullptr)
5358 return;
5359
5360 for (;; tileElement++)
5361 {
5362 if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
5363 {
5364 if (NextLoc.z == tileElement->GetBaseZ())
5365 break;
5366 }
5367 if (tileElement->IsLastForTile())
5368 {
5369 return;
5370 }
5371 }
5372
5373 int32_t positions_free = 15;
5374
5375 if (tileElement->AsPath()->HasAddition())
5376 {
5377 if (!tileElement->AsPath()->AdditionIsGhost())
5378 {
5379 auto* pathAddEntry = tileElement->AsPath()->GetAdditionEntry();
5380 if (pathAddEntry == nullptr)
5381 {
5382 return;
5383 }
5384
5385 if (!(pathAddEntry->flags & PATH_BIT_FLAG_IS_BENCH))
5386 positions_free = 9;
5387 }
5388 }
5389
5390 int32_t edges = (tileElement->AsPath()->GetEdges()) ^ 0xF;
5391 if (edges == 0)
5392 return;
5393
5394 uint8_t chosen_edge = scenario_rand() & 0x3;
5395
5396 for (; !(edges & (1 << chosen_edge));)
5397 chosen_edge = (chosen_edge + 1) & 3;
5398
5399 ride_id_t ride_to_view;
5400 uint8_t ride_seat_to_view;
5401 if (!peep_find_ride_to_look_at(this, chosen_edge, &ride_to_view, &ride_seat_to_view))
5402 return;
5403
5404 // Check if there is a peep watching (and if there is place for us)
5405 for (auto peep : EntityTileList<Peep>({ x, y }))
5406 {
5407 if (peep->State != PeepState::Watching)
5408 continue;
5409
5410 if (z != peep->z)
5411 continue;
5412
5413 if ((peep->Var37 & 0x3) != chosen_edge)
5414 continue;
5415
5416 positions_free &= ~(1 << ((peep->Var37 & 0x1C) >> 2));
5417 }
5418
5419 if (!positions_free)
5420 return;
5421
5422 uint8_t chosen_position = scenario_rand() & 0x3;
5423
5424 for (; !(positions_free & (1 << chosen_position));)
5425 chosen_position = (chosen_position + 1) & 3;
5426
5427 CurrentRide = ride_to_view;
5428 CurrentSeat = ride_seat_to_view;
5429 Var37 = chosen_edge | (chosen_position << 2);
5430
5431 SetState(PeepState::Watching);
5432 SubState = 0;
5433
5434 int32_t destX = (x & 0xFFE0) + _WatchingPositionOffsets[Var37 & 0x1F].x;
5435 int32_t destY = (y & 0xFFE0) + _WatchingPositionOffsets[Var37 & 0x1F].y;
5436
5437 SetDestination({ destX, destY }, 3);
5438
5439 if (CurrentSeat & 1)
5440 {
5441 InsertNewThought(PeepThoughtType::NewRide);
5442 }
5443 if (CurrentRide == RIDE_ID_NULL)
5444 {
5445 InsertNewThought(PeepThoughtType::Scenery);
5446 }
5447 }
5448
5449 /**
5450 *
5451 * rct2: 0x69185D
5452 */
UpdateQueuing()5453 void Guest::UpdateQueuing()
5454 {
5455 if (!CheckForPath())
5456 {
5457 RemoveFromQueue();
5458 return;
5459 }
5460 auto ride = get_ride(CurrentRide);
5461 if (ride == nullptr || ride->status != RideStatus::Open)
5462 {
5463 RemoveFromQueue();
5464 SetState(PeepState::One);
5465 return;
5466 }
5467
5468 // If not in the queue then at front of queue
5469 if (RideSubState != PeepRideSubState::InQueue)
5470 {
5471 bool is_front = true;
5472 // Fix #4819: Occasionally the peep->GuestNextInQueue is incorrectly set
5473 // to prevent this from causing the peeps to enter a loop
5474 // first check if the next in queue is actually nearby
5475 // if they are not then it's safe to assume that this is
5476 // the front of the queue.
5477 Peep* nextGuest = GetEntity<Guest>(GuestNextInQueue);
5478 if (nextGuest != nullptr)
5479 {
5480 if (abs(nextGuest->x - x) < 32 && abs(nextGuest->y - y) < 32)
5481 {
5482 is_front = false;
5483 }
5484 }
5485
5486 if (is_front)
5487 {
5488 // Happens every time peep goes onto ride.
5489 DestinationTolerance = 0;
5490 SetState(PeepState::QueuingFront);
5491 RideSubState = PeepRideSubState::AtEntrance;
5492 return;
5493 }
5494
5495 // Give up queueing for the ride
5496 sprite_direction ^= (1 << 4);
5497 Invalidate();
5498 RemoveFromQueue();
5499 SetState(PeepState::One);
5500 return;
5501 }
5502
5503 uint8_t pathingResult;
5504 PerformNextAction(pathingResult);
5505 if (!IsActionInterruptable())
5506 return;
5507 if (SpriteType == PeepSpriteType::Normal)
5508 {
5509 if (TimeInQueue >= 2000 && (0xFFFF & scenario_rand()) <= 119)
5510 {
5511 // Eat Food/Look at watch
5512 Action = PeepActionType::EatFood;
5513 ActionFrame = 0;
5514 ActionSpriteImageOffset = 0;
5515 UpdateCurrentActionSpriteType();
5516 }
5517 if (TimeInQueue >= 3500 && (0xFFFF & scenario_rand()) <= 93)
5518 {
5519 // Create the I have been waiting in line ages thought
5520 InsertNewThought(PeepThoughtType::QueuingAges, CurrentRide);
5521 }
5522 }
5523 else
5524 {
5525 if (!(TimeInQueue & 0x3F) && IsActionIdle() && NextActionSpriteType == PeepActionSpriteType::WatchRide)
5526 {
5527 switch (SpriteType)
5528 {
5529 case PeepSpriteType::IceCream:
5530 case PeepSpriteType::Chips:
5531 case PeepSpriteType::Burger:
5532 case PeepSpriteType::Drink:
5533 case PeepSpriteType::Candyfloss:
5534 case PeepSpriteType::Pizza:
5535 case PeepSpriteType::Popcorn:
5536 case PeepSpriteType::HotDog:
5537 case PeepSpriteType::Tentacle:
5538 case PeepSpriteType::ToffeeApple:
5539 case PeepSpriteType::Doughnut:
5540 case PeepSpriteType::Coffee:
5541 case PeepSpriteType::Chicken:
5542 case PeepSpriteType::Lemonade:
5543 case PeepSpriteType::Pretzel:
5544 case PeepSpriteType::SuJongkwa:
5545 case PeepSpriteType::Juice:
5546 case PeepSpriteType::FunnelCake:
5547 case PeepSpriteType::Noodles:
5548 case PeepSpriteType::Sausage:
5549 case PeepSpriteType::Soup:
5550 case PeepSpriteType::Sandwich:
5551 // Eat food
5552 Action = PeepActionType::EatFood;
5553 ActionFrame = 0;
5554 ActionSpriteImageOffset = 0;
5555 UpdateCurrentActionSpriteType();
5556 break;
5557 default:
5558 break;
5559 }
5560 }
5561 }
5562 if (TimeInQueue < 4300)
5563 return;
5564
5565 if (Happiness <= 65 && (0xFFFF & scenario_rand()) < 2184)
5566 {
5567 // Give up queueing for the ride
5568 sprite_direction ^= (1 << 4);
5569 Invalidate();
5570 RemoveFromQueue();
5571 SetState(PeepState::One);
5572 }
5573 }
5574
5575 /**
5576 * rct2: 0x691451
5577 */
UpdateEnteringPark()5578 void Guest::UpdateEnteringPark()
5579 {
5580 if (Var37 != 1)
5581 {
5582 uint8_t pathingResult;
5583 PerformNextAction(pathingResult);
5584 if ((pathingResult & PATHING_OUTSIDE_PARK))
5585 {
5586 decrement_guests_heading_for_park();
5587 peep_sprite_remove(this);
5588 }
5589 return;
5590 }
5591 if (auto loc = UpdateAction(); loc.has_value())
5592 {
5593 MoveTo({ loc.value(), z });
5594 return;
5595 }
5596 SetState(PeepState::Falling);
5597
5598 OutsideOfPark = false;
5599 ParkEntryTime = gCurrentTicks;
5600 increment_guests_in_park();
5601 decrement_guests_heading_for_park();
5602 auto intent = Intent(INTENT_ACTION_UPDATE_GUEST_COUNT);
5603 context_broadcast_intent(&intent);
5604 }
5605
5606 /**
5607 *
5608 * rct2: 0x6914CD
5609 */
UpdateLeavingPark()5610 void Guest::UpdateLeavingPark()
5611 {
5612 if (Var37 != 0)
5613 {
5614 uint8_t pathingResult;
5615 PerformNextAction(pathingResult);
5616 if (!(pathingResult & PATHING_OUTSIDE_PARK))
5617 return;
5618 peep_sprite_remove(this);
5619 return;
5620 }
5621
5622 if (auto loc = UpdateAction(); loc.has_value())
5623 {
5624 MoveTo({ loc.value(), z });
5625 return;
5626 }
5627
5628 OutsideOfPark = true;
5629 DestinationTolerance = 5;
5630 decrement_guests_in_park();
5631 auto intent = Intent(INTENT_ACTION_UPDATE_GUEST_COUNT);
5632 context_broadcast_intent(&intent);
5633 Var37 = 1;
5634
5635 window_invalidate_by_class(WC_GUEST_LIST);
5636 uint8_t pathingResult;
5637 PerformNextAction(pathingResult);
5638 if (!(pathingResult & PATHING_OUTSIDE_PARK))
5639 return;
5640 Remove();
5641 }
5642
5643 /**
5644 *
5645 * rct2: 0x6916D6
5646 */
UpdateWatching()5647 void Guest::UpdateWatching()
5648 {
5649 if (SubState == 0)
5650 {
5651 if (!CheckForPath())
5652 return;
5653 uint8_t pathingResult;
5654 PerformNextAction(pathingResult);
5655 if (!(pathingResult & PATHING_DESTINATION_REACHED))
5656 return;
5657
5658 SetDestination(GetLocation());
5659
5660 sprite_direction = (Var37 & 3) * 8;
5661
5662 Action = PeepActionType::Idle;
5663 NextActionSpriteType = PeepActionSpriteType::WatchRide;
5664
5665 SwitchNextActionSpriteType();
5666
5667 SubState++;
5668
5669 TimeToStand = std::clamp(((129 - Energy) * 16 + 50) / 2, 0, 255);
5670 UpdateSpriteType();
5671 }
5672 else if (SubState == 1)
5673 {
5674 if (!IsActionInterruptable())
5675 {
5676 // 6917F6
5677 UpdateAction();
5678 Invalidate();
5679 if (!IsActionWalking())
5680 return;
5681 Action = PeepActionType::Idle;
5682 }
5683 else
5684 {
5685 if (HasFoodOrDrink())
5686 {
5687 if ((scenario_rand() & 0xFFFF) <= 1310)
5688 {
5689 Action = PeepActionType::EatFood;
5690 ActionFrame = 0;
5691 ActionSpriteImageOffset = 0;
5692 UpdateCurrentActionSpriteType();
5693 return;
5694 }
5695 }
5696
5697 if ((scenario_rand() & 0xFFFF) <= 655)
5698 {
5699 Action = PeepActionType::TakePhoto;
5700 ActionFrame = 0;
5701 ActionSpriteImageOffset = 0;
5702 UpdateCurrentActionSpriteType();
5703 return;
5704 }
5705
5706 if ((StandingFlags & 1))
5707 {
5708 if ((scenario_rand() & 0xFFFF) <= 655)
5709 {
5710 Action = PeepActionType::Wave;
5711 ActionFrame = 0;
5712 ActionSpriteImageOffset = 0;
5713 UpdateCurrentActionSpriteType();
5714 return;
5715 }
5716 }
5717 }
5718
5719 StandingFlags ^= (1 << 7);
5720 if (!(StandingFlags & (1 << 7)))
5721 return;
5722
5723 TimeToStand--;
5724 if (TimeToStand != 0)
5725 return;
5726
5727 SetState(PeepState::Walking);
5728 UpdateSpriteType();
5729 // Send peep to the centre of current tile.
5730
5731 auto destination = GetLocation().ToTileCentre();
5732 SetDestination(destination, 5);
5733 UpdateCurrentActionSpriteType();
5734 }
5735 }
5736
5737 /**
5738 *
5739 * rct2: 0x00691089
5740 */
UpdateUsingBin()5741 void Guest::UpdateUsingBin()
5742 {
5743 switch (UsingBinSubState)
5744 {
5745 case PeepUsingBinSubState::WalkingToBin:
5746 {
5747 if (!CheckForPath())
5748 return;
5749
5750 uint8_t pathingResult;
5751 PerformNextAction(pathingResult);
5752 if (pathingResult & PATHING_DESTINATION_REACHED)
5753 {
5754 UsingBinSubState = PeepUsingBinSubState::GoingBack;
5755 }
5756 break;
5757 }
5758 case PeepUsingBinSubState::GoingBack:
5759 {
5760 if (!IsActionWalking())
5761 {
5762 UpdateAction();
5763 Invalidate();
5764 return;
5765 }
5766
5767 PathElement* foundElement = nullptr;
5768 for (auto* pathElement : TileElementsView<PathElement>(NextLoc))
5769 {
5770 if (pathElement->GetBaseZ() != NextLoc.z)
5771 continue;
5772
5773 if (!pathElement->HasAddition())
5774 break;
5775
5776 auto* pathAddEntry = pathElement->GetAdditionEntry();
5777 if (!(pathAddEntry->flags & PATH_BIT_FLAG_IS_BIN))
5778 break;
5779
5780 if (pathElement->IsBroken())
5781 break;
5782
5783 if (pathElement->AdditionIsGhost())
5784 break;
5785
5786 foundElement = pathElement;
5787 break;
5788 }
5789
5790 if (foundElement == nullptr)
5791 {
5792 StateReset();
5793 return;
5794 }
5795
5796 // Bin selection is one of 4 corners
5797 uint8_t selectedBin = Var37 * 2;
5798
5799 // This counts down 2 = No rubbish, 0 = full
5800 uint8_t spaceLeftInBin = 0x3 & (foundElement->GetAdditionStatus() >> selectedBin);
5801 uint64_t emptyContainers = GetEmptyContainerFlags();
5802
5803 for (uint8_t curContainer = 0; curContainer < 64; curContainer++)
5804 {
5805 if (!(emptyContainers & (1ULL << curContainer)))
5806 continue;
5807
5808 auto item = ShopItem(curContainer);
5809 if (spaceLeftInBin != 0)
5810 {
5811 // OpenRCT2 modification: This previously used
5812 // the tick count as a simple random function
5813 // switched to scenario_rand as it is more reliable
5814 if ((scenario_rand() & 7) == 0)
5815 spaceLeftInBin--;
5816 RemoveItem(item);
5817 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_INVENTORY;
5818 UpdateSpriteType();
5819 continue;
5820 }
5821
5822 auto litterType = Litter::Type(GetShopItemDescriptor(item).Type);
5823
5824 int32_t litterX = x + (scenario_rand() & 7) - 3;
5825 int32_t litterY = y + (scenario_rand() & 7) - 3;
5826
5827 Litter::Create({ litterX, litterY, z, static_cast<Direction>(scenario_rand() & 3) }, litterType);
5828 RemoveItem(item);
5829 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_INVENTORY;
5830
5831 UpdateSpriteType();
5832 }
5833
5834 uint8_t additionStatus = foundElement->GetAdditionStatus();
5835 // Place new amount in bin by first clearing the value
5836 additionStatus &= ~(3 << selectedBin);
5837 // Then placing the new value.
5838 additionStatus |= spaceLeftInBin << selectedBin;
5839 foundElement->SetAdditionStatus(additionStatus);
5840
5841 map_invalidate_tile_zoom0({ NextLoc, foundElement->GetBaseZ(), foundElement->GetClearanceZ() });
5842 StateReset();
5843 break;
5844 }
5845 default:
5846 Guard::Assert(false, "Invalid sub state");
5847 break;
5848 }
5849 }
5850
5851 /* Simplifies 0x690582. Returns true if should find bench*/
ShouldFindBench()5852 bool Guest::ShouldFindBench()
5853 {
5854 if (PeepFlags & PEEP_FLAGS_LEAVING_PARK)
5855 {
5856 return false;
5857 }
5858
5859 if (HasFoodOrDrink())
5860 {
5861 if (Hunger < 128 || Happiness < 128)
5862 {
5863 if (!GetNextIsSurface() && !GetNextIsSloped())
5864 {
5865 return true;
5866 }
5867 }
5868 }
5869
5870 if (Nausea <= 170 && Energy > 50)
5871 {
5872 return false;
5873 }
5874
5875 return !GetNextIsSurface() && !GetNextIsSloped();
5876 }
5877
FindBench(const CoordsXYZ & loc)5878 static PathElement* FindBench(const CoordsXYZ& loc)
5879 {
5880 for (auto* pathElement : TileElementsView<PathElement>(loc))
5881 {
5882 if (pathElement->GetBaseZ() != loc.z)
5883 continue;
5884
5885 if (!pathElement->HasAddition())
5886 continue;
5887
5888 auto* pathAddEntry = pathElement->GetAdditionEntry();
5889 if (pathAddEntry == nullptr || !(pathAddEntry->flags & PATH_BIT_FLAG_IS_BENCH))
5890 continue;
5891
5892 if (pathElement->IsBroken())
5893 continue;
5894
5895 if (pathElement->AdditionIsGhost())
5896 continue;
5897
5898 return pathElement;
5899 }
5900
5901 return nullptr;
5902 }
5903
5904 /**
5905 *
5906 * rct2: 0x00690582
5907 * Returns true when the guest wants to sit down and has found a bench to sit on
5908 */
UpdateWalkingFindBench()5909 bool Guest::UpdateWalkingFindBench()
5910 {
5911 if (!ShouldFindBench())
5912 return false;
5913
5914 auto* pathElement = FindBench(NextLoc);
5915 if (pathElement == nullptr)
5916 return false;
5917
5918 int32_t edges = pathElement->GetEdges() ^ 0xF;
5919 if (edges == 0)
5920 return false;
5921 uint8_t chosen_edge = scenario_rand() & 0x3;
5922
5923 for (; !(edges & (1 << chosen_edge));)
5924 chosen_edge = (chosen_edge + 1) & 0x3;
5925
5926 uint8_t free_edge = 3;
5927
5928 // Check if there is no peep sitting in chosen_edge
5929 for (auto peep : EntityTileList<Peep>({ x, y }))
5930 {
5931 if (peep->State != PeepState::Sitting)
5932 continue;
5933
5934 if (z != peep->z)
5935 continue;
5936
5937 if ((peep->Var37 & 0x3) != chosen_edge)
5938 continue;
5939
5940 free_edge &= ~(1 << ((peep->Var37 & 0x4) >> 2));
5941 }
5942
5943 if (!free_edge)
5944 return false;
5945
5946 free_edge ^= 0x3;
5947 if (!free_edge)
5948 {
5949 if (scenario_rand() & 0x8000000)
5950 free_edge = 1;
5951 }
5952
5953 Var37 = ((free_edge & 1) << 2) | chosen_edge;
5954
5955 SetState(PeepState::Sitting);
5956
5957 SittingSubState = PeepSittingSubState::TryingToSit;
5958
5959 int32_t benchX = (x & 0xFFE0) + BenchUseOffsets[Var37 & 0x7].x;
5960 int32_t benchY = (y & 0xFFE0) + BenchUseOffsets[Var37 & 0x7].y;
5961
5962 SetDestination({ benchX, benchY }, 3);
5963
5964 return true;
5965 }
5966
FindBin(const CoordsXYZ & loc)5967 static PathElement* FindBin(const CoordsXYZ& loc)
5968 {
5969 for (auto* pathElement : TileElementsView<PathElement>(loc))
5970 {
5971 if (pathElement->GetBaseZ() != loc.z)
5972 continue;
5973
5974 if (!pathElement->HasAddition())
5975 continue;
5976
5977 auto* pathAddEntry = pathElement->GetAdditionEntry();
5978 if (pathAddEntry == nullptr || !(pathAddEntry->flags & PATH_BIT_FLAG_IS_BIN))
5979 continue;
5980
5981 if (pathElement->IsBroken())
5982 continue;
5983
5984 if (pathElement->AdditionIsGhost())
5985 continue;
5986
5987 return pathElement;
5988 }
5989
5990 return nullptr;
5991 }
5992
UpdateWalkingFindBin()5993 bool Guest::UpdateWalkingFindBin()
5994 {
5995 auto peep = this;
5996 if (!peep->HasEmptyContainer())
5997 return false;
5998
5999 if (peep->GetNextIsSurface())
6000 return false;
6001
6002 auto* pathElement = FindBin(peep->NextLoc);
6003 if (pathElement == nullptr)
6004 return false;
6005
6006 int32_t edges = (pathElement->GetEdges()) ^ 0xF;
6007 if (edges == 0)
6008 return false;
6009
6010 uint8_t chosen_edge = scenario_rand() & 0x3;
6011
6012 // Note: Bin quantity is inverted 0 = full, 3 = empty
6013 uint8_t bin_quantities = pathElement->GetAdditionStatus();
6014
6015 // Rotate the bin to the correct edge. Makes it easier for next calc.
6016 bin_quantities = Numerics::ror8(Numerics::ror8(bin_quantities, chosen_edge), chosen_edge);
6017
6018 for (uint8_t free_edge = 4; free_edge != 0; free_edge--)
6019 {
6020 // If not full
6021 if (bin_quantities & 0x3)
6022 {
6023 if (edges & (1 << chosen_edge))
6024 break;
6025 }
6026 chosen_edge = (chosen_edge + 1) & 0x3;
6027 bin_quantities = Numerics::ror8(bin_quantities, 2);
6028 if ((free_edge - 1) == 0)
6029 return 0;
6030 }
6031
6032 peep->Var37 = chosen_edge;
6033
6034 peep->SetState(PeepState::UsingBin);
6035 peep->UsingBinSubState = PeepUsingBinSubState::WalkingToBin;
6036
6037 int32_t binX = (peep->x & 0xFFE0) + BinUseOffsets[peep->Var37 & 0x3].x;
6038 int32_t binY = (peep->y & 0xFFE0) + BinUseOffsets[peep->Var37 & 0x3].y;
6039
6040 peep->SetDestination({ binX, binY }, 3);
6041
6042 return true;
6043 }
6044
FindBreakableElement(const CoordsXYZ & loc)6045 static PathElement* FindBreakableElement(const CoordsXYZ& loc)
6046 {
6047 for (auto* pathElement : TileElementsView<PathElement>(loc))
6048 {
6049 if (pathElement->GetBaseZ() != loc.z)
6050 continue;
6051
6052 if (!pathElement->HasAddition())
6053 continue;
6054
6055 auto* pathAddEntry = pathElement->GetAdditionEntry();
6056 if (pathAddEntry == nullptr || !(pathAddEntry->flags & PATH_BIT_FLAG_BREAKABLE))
6057 continue;
6058
6059 if (pathElement->IsBroken())
6060 continue;
6061
6062 if (pathElement->AdditionIsGhost())
6063 continue;
6064
6065 return pathElement;
6066 }
6067
6068 return nullptr;
6069 }
6070
6071 /**
6072 *
6073 * rct2: 0x00690848
6074 */
peep_update_walking_break_scenery(Guest * peep)6075 static void peep_update_walking_break_scenery(Guest* peep)
6076 {
6077 if (gCheatsDisableVandalism)
6078 return;
6079
6080 if (!(peep->PeepFlags & PEEP_FLAGS_ANGRY))
6081 {
6082 if (peep->Happiness >= 48)
6083 return;
6084 if (peep->Energy < 85)
6085 return;
6086 if (peep->State != PeepState::Walking)
6087 return;
6088
6089 if ((peep->LitterCount & 0xC0) != 0xC0 && (peep->DisgustingCount & 0xC0) != 0xC0)
6090 return;
6091
6092 if ((scenario_rand() & 0xFFFF) > 3276)
6093 return;
6094 }
6095
6096 if (peep->GetNextIsSurface())
6097 return;
6098
6099 auto* tileElement = FindBreakableElement(peep->NextLoc);
6100 if (tileElement == nullptr)
6101 return;
6102
6103 int32_t edges = tileElement->GetEdges();
6104 if (edges == 0xF)
6105 return;
6106
6107 // Check if a peep is already sitting on the bench. If so, do not vandalise it.
6108 for (auto peep2 : EntityTileList<Peep>({ peep->x, peep->y }))
6109 {
6110 if ((peep2->State != PeepState::Sitting) || (peep->z != peep2->z))
6111 {
6112 continue;
6113 }
6114
6115 return;
6116 }
6117
6118 for (auto inner_peep : EntityList<Staff>())
6119 {
6120 if (inner_peep->AssignedStaffType != StaffType::Security)
6121 continue;
6122
6123 if (inner_peep->x == LOCATION_NULL)
6124 continue;
6125
6126 int32_t x_diff = abs(inner_peep->x - peep->x);
6127 int32_t y_diff = abs(inner_peep->y - peep->y);
6128
6129 if (std::max(x_diff, y_diff) < 224)
6130 {
6131 inner_peep->StaffVandalsStopped++;
6132 return;
6133 }
6134 }
6135
6136 tileElement->SetIsBroken(true);
6137
6138 map_invalidate_tile_zoom1({ peep->NextLoc, tileElement->GetBaseZ(), tileElement->GetBaseZ() + 32 });
6139
6140 peep->Angriness = 16;
6141 }
6142
6143 /**
6144 * rct2: 0x0069101A
6145 *
6146 * @return (CF)
6147 */
peep_should_watch_ride(TileElement * tileElement)6148 static bool peep_should_watch_ride(TileElement* tileElement)
6149 {
6150 // Ghosts are purely this-client-side and should not cause any interaction,
6151 // as that may lead to a desync.
6152 if (network_get_mode() != NETWORK_MODE_NONE)
6153 {
6154 if (tileElement->IsGhost())
6155 return false;
6156 }
6157
6158 auto ride = get_ride(tileElement->AsTrack()->GetRideIndex());
6159 if (ride == nullptr || !ride->IsRide())
6160 {
6161 return false;
6162 }
6163
6164 // This is most likely to have peeps watch new rides
6165 if (ride->excitement == RIDE_RATING_UNDEFINED)
6166 {
6167 return true;
6168 }
6169
6170 if (ride->excitement >= RIDE_RATING(4, 70))
6171 {
6172 return true;
6173 }
6174
6175 if (ride->intensity >= RIDE_RATING(4, 50))
6176 {
6177 return true;
6178 }
6179
6180 if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_INTERESTING_TO_LOOK_AT))
6181 {
6182 if ((scenario_rand() & 0xFFFF) > 0x3333)
6183 {
6184 return false;
6185 }
6186 }
6187 else if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_SLIGHTLY_INTERESTING_TO_LOOK_AT))
6188 {
6189 if ((scenario_rand() & 0xFFFF) > 0x1000)
6190 {
6191 return false;
6192 }
6193 }
6194 else
6195 {
6196 return false;
6197 }
6198
6199 return true;
6200 }
6201
loc_690FD0(Peep * peep,ride_id_t * rideToView,uint8_t * rideSeatToView,TileElement * tileElement)6202 bool loc_690FD0(Peep* peep, ride_id_t* rideToView, uint8_t* rideSeatToView, TileElement* tileElement)
6203 {
6204 auto ride = get_ride(tileElement->AsTrack()->GetRideIndex());
6205 if (ride == nullptr)
6206 return false;
6207
6208 *rideToView = ride->id;
6209 if (ride->excitement == RIDE_RATING_UNDEFINED)
6210 {
6211 *rideSeatToView = 1;
6212 if (ride->status != RideStatus::Open)
6213 {
6214 if (tileElement->GetClearanceZ() > peep->NextLoc.z + (8 * COORDS_Z_STEP))
6215 {
6216 *rideSeatToView |= (1 << 1);
6217 }
6218
6219 return true;
6220 }
6221 }
6222 else
6223 {
6224 *rideSeatToView = 0;
6225 if (ride->status == RideStatus::Open && !(ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN))
6226 {
6227 if (tileElement->GetClearanceZ() > peep->NextLoc.z + (8 * COORDS_Z_STEP))
6228 {
6229 *rideSeatToView = 0x02;
6230 }
6231
6232 return true;
6233 }
6234 }
6235
6236 return false;
6237 }
6238
6239 /**
6240 *
6241 * rct2: 0x00690B99
6242 *
6243 * @param edge (eax)
6244 * @param peep (esi)
6245 * @param[out] rideToView (cl)
6246 * @param[out] rideSeatToView (ch)
6247 * @return !CF
6248 */
peep_find_ride_to_look_at(Peep * peep,uint8_t edge,ride_id_t * rideToView,uint8_t * rideSeatToView)6249 static bool peep_find_ride_to_look_at(Peep* peep, uint8_t edge, ride_id_t* rideToView, uint8_t* rideSeatToView)
6250 {
6251 TileElement* tileElement;
6252
6253 auto surfaceElement = map_get_surface_element_at(peep->NextLoc);
6254
6255 tileElement = reinterpret_cast<TileElement*>(surfaceElement);
6256 if (tileElement == nullptr)
6257 {
6258 return false;
6259 }
6260
6261 do
6262 {
6263 // Ghosts are purely this-client-side and should not cause any interaction,
6264 // as that may lead to a desync.
6265 if (network_get_mode() != NETWORK_MODE_NONE)
6266 {
6267 if (tileElement->IsGhost())
6268 continue;
6269 }
6270 if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
6271 continue;
6272 if (tileElement->GetDirection() != edge)
6273 continue;
6274 auto wallEntry = tileElement->AsWall()->GetEntry();
6275 if (wallEntry == nullptr || (wallEntry->flags2 & WALL_SCENERY_2_IS_OPAQUE))
6276 continue;
6277 if (peep->NextLoc.z + (4 * COORDS_Z_STEP) <= tileElement->GetBaseZ())
6278 continue;
6279 if (peep->NextLoc.z + (1 * COORDS_Z_STEP) >= tileElement->GetClearanceZ())
6280 continue;
6281
6282 return false;
6283 } while (!(tileElement++)->IsLastForTile());
6284
6285 uint16_t x = peep->NextLoc.x + CoordsDirectionDelta[edge].x;
6286 uint16_t y = peep->NextLoc.y + CoordsDirectionDelta[edge].y;
6287 if (!map_is_location_valid({ x, y }))
6288 {
6289 return false;
6290 }
6291
6292 surfaceElement = map_get_surface_element_at(CoordsXY{ x, y });
6293
6294 tileElement = reinterpret_cast<TileElement*>(surfaceElement);
6295 if (tileElement == nullptr)
6296 {
6297 return false;
6298 }
6299
6300 do
6301 {
6302 // Ghosts are purely this-client-side and should not cause any interaction,
6303 // as that may lead to a desync.
6304 if (network_get_mode() != NETWORK_MODE_NONE)
6305 {
6306 if (tileElement->IsGhost())
6307 continue;
6308 }
6309 if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
6310 continue;
6311 if (direction_reverse(tileElement->GetDirection()) != edge)
6312 continue;
6313 auto wallEntry = tileElement->AsWall()->GetEntry();
6314 if (wallEntry == nullptr || (wallEntry->flags2 & WALL_SCENERY_2_IS_OPAQUE))
6315 continue;
6316 // TODO: Check whether this shouldn't be <=, as the other loops use. If so, also extract as loop A.
6317 if (peep->NextLoc.z + (4 * COORDS_Z_STEP) >= tileElement->GetBaseZ())
6318 continue;
6319 if (peep->NextLoc.z + (1 * COORDS_Z_STEP) >= tileElement->GetClearanceZ())
6320 continue;
6321
6322 return false;
6323 } while (!(tileElement++)->IsLastForTile());
6324
6325 // TODO: Extract loop B
6326 tileElement = reinterpret_cast<TileElement*>(surfaceElement);
6327 do
6328 {
6329 // Ghosts are purely this-client-side and should not cause any interaction,
6330 // as that may lead to a desync.
6331 if (network_get_mode() != NETWORK_MODE_NONE)
6332 {
6333 if (tileElement->IsGhost())
6334 continue;
6335 }
6336
6337 if (tileElement->GetClearanceZ() + (1 * COORDS_Z_STEP) < peep->NextLoc.z)
6338 continue;
6339 if (peep->NextLoc.z + (6 * COORDS_Z_STEP) < tileElement->GetBaseZ())
6340 continue;
6341
6342 if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
6343 {
6344 if (peep_should_watch_ride(tileElement))
6345 {
6346 return loc_690FD0(peep, rideToView, rideSeatToView, tileElement);
6347 }
6348 }
6349
6350 if (tileElement->GetType() == TILE_ELEMENT_TYPE_LARGE_SCENERY)
6351 {
6352 const auto* sceneryEntry = tileElement->AsLargeScenery()->GetEntry();
6353 if (sceneryEntry == nullptr || !(sceneryEntry->flags & LARGE_SCENERY_FLAG_PHOTOGENIC))
6354 {
6355 continue;
6356 }
6357
6358 *rideSeatToView = 0;
6359 if (tileElement->GetClearanceZ() >= peep->NextLoc.z + (8 * COORDS_Z_STEP))
6360 {
6361 *rideSeatToView = 0x02;
6362 }
6363
6364 *rideToView = RIDE_ID_NULL;
6365
6366 return true;
6367 }
6368 } while (!(tileElement++)->IsLastForTile());
6369
6370 // TODO: Extract loop C
6371 tileElement = reinterpret_cast<TileElement*>(surfaceElement);
6372 do
6373 {
6374 // Ghosts are purely this-client-side and should not cause any interaction,
6375 // as that may lead to a desync.
6376 if (network_get_mode() != NETWORK_MODE_NONE)
6377 {
6378 if (tileElement->IsGhost())
6379 continue;
6380 }
6381 if (tileElement->GetClearanceZ() + (1 * COORDS_Z_STEP) < peep->NextLoc.z)
6382 continue;
6383 if (peep->NextLoc.z + (6 * COORDS_Z_STEP) < tileElement->GetBaseZ())
6384 continue;
6385 if (tileElement->GetType() == TILE_ELEMENT_TYPE_SURFACE)
6386 continue;
6387 if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
6388 continue;
6389
6390 if (tileElement->GetType() == TILE_ELEMENT_TYPE_WALL)
6391 {
6392 auto wallEntry = tileElement->AsWall()->GetEntry();
6393 if (wallEntry == nullptr || (wallEntry->flags2 & WALL_SCENERY_2_IS_OPAQUE))
6394 {
6395 continue;
6396 }
6397 }
6398
6399 return false;
6400 } while (!(tileElement++)->IsLastForTile());
6401
6402 x += CoordsDirectionDelta[edge].x;
6403 y += CoordsDirectionDelta[edge].y;
6404 if (!map_is_location_valid({ x, y }))
6405 {
6406 return false;
6407 }
6408
6409 surfaceElement = map_get_surface_element_at(CoordsXY{ x, y });
6410
6411 // TODO: extract loop A
6412 tileElement = reinterpret_cast<TileElement*>(surfaceElement);
6413
6414 if (tileElement == nullptr)
6415 {
6416 return false;
6417 }
6418
6419 do
6420 {
6421 // Ghosts are purely this-client-side and should not cause any interaction,
6422 // as that may lead to a desync.
6423 if (network_get_mode() != NETWORK_MODE_NONE)
6424 {
6425 if (tileElement->IsGhost())
6426 continue;
6427 }
6428 if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
6429 continue;
6430 if (direction_reverse(tileElement->GetDirection()) != edge)
6431 continue;
6432 auto wallEntry = tileElement->AsWall()->GetEntry();
6433 if (wallEntry == nullptr || (wallEntry->flags2 & WALL_SCENERY_2_IS_OPAQUE))
6434 continue;
6435 if (peep->NextLoc.z + (6 * COORDS_Z_STEP) <= tileElement->GetBaseZ())
6436 continue;
6437 if (peep->NextLoc.z >= tileElement->GetClearanceZ())
6438 continue;
6439
6440 return false;
6441 } while (!(tileElement++)->IsLastForTile());
6442
6443 // TODO: Extract loop B
6444 tileElement = reinterpret_cast<TileElement*>(surfaceElement);
6445 do
6446 {
6447 // Ghosts are purely this-client-side and should not cause any interaction,
6448 // as that may lead to a desync.
6449 if (network_get_mode() != NETWORK_MODE_NONE)
6450 {
6451 if (tileElement->IsGhost())
6452 continue;
6453 }
6454 if (tileElement->GetClearanceZ() + (1 * COORDS_Z_STEP) < peep->NextLoc.z)
6455 continue;
6456 if (peep->NextLoc.z + (8 * COORDS_Z_STEP) < tileElement->GetBaseZ())
6457 continue;
6458
6459 if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
6460 {
6461 if (peep_should_watch_ride(tileElement))
6462 {
6463 return loc_690FD0(peep, rideToView, rideSeatToView, tileElement);
6464 }
6465 }
6466
6467 if (tileElement->GetType() == TILE_ELEMENT_TYPE_LARGE_SCENERY)
6468 {
6469 auto* sceneryEntry = tileElement->AsLargeScenery()->GetEntry();
6470 if (!(sceneryEntry == nullptr || sceneryEntry->flags & LARGE_SCENERY_FLAG_PHOTOGENIC))
6471 {
6472 continue;
6473 }
6474
6475 *rideSeatToView = 0;
6476 if (tileElement->GetClearanceZ() >= peep->NextLoc.z + (8 * COORDS_Z_STEP))
6477 {
6478 *rideSeatToView = 0x02;
6479 }
6480
6481 *rideToView = RIDE_ID_NULL;
6482
6483 return true;
6484 }
6485 } while (!(tileElement++)->IsLastForTile());
6486
6487 // TODO: Extract loop C
6488 tileElement = reinterpret_cast<TileElement*>(surfaceElement);
6489 do
6490 {
6491 // Ghosts are purely this-client-side and should not cause any interaction,
6492 // as that may lead to a desync.
6493 if (network_get_mode() != NETWORK_MODE_NONE)
6494 {
6495 if (tileElement->IsGhost())
6496 continue;
6497 }
6498 if (tileElement->GetClearanceZ() + (1 * COORDS_Z_STEP) < peep->NextLoc.z)
6499 continue;
6500 if (peep->NextLoc.z + (8 * COORDS_Z_STEP) < tileElement->GetBaseZ())
6501 continue;
6502 if (tileElement->GetType() == TILE_ELEMENT_TYPE_SURFACE)
6503 continue;
6504 if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
6505 continue;
6506
6507 if (tileElement->GetType() == TILE_ELEMENT_TYPE_WALL)
6508 {
6509 auto wallEntry = tileElement->AsWall()->GetEntry();
6510 if (wallEntry == nullptr || (wallEntry->flags2 & WALL_SCENERY_2_IS_OPAQUE))
6511 {
6512 continue;
6513 }
6514 }
6515
6516 return false;
6517 } while (!(tileElement++)->IsLastForTile());
6518
6519 x += CoordsDirectionDelta[edge].x;
6520 y += CoordsDirectionDelta[edge].y;
6521 if (!map_is_location_valid({ x, y }))
6522 {
6523 return false;
6524 }
6525
6526 surfaceElement = map_get_surface_element_at(CoordsXY{ x, y });
6527
6528 // TODO: extract loop A
6529 tileElement = reinterpret_cast<TileElement*>(surfaceElement);
6530 if (tileElement == nullptr)
6531 {
6532 return false;
6533 }
6534
6535 do
6536 {
6537 // Ghosts are purely this-client-side and should not cause any interaction,
6538 // as that may lead to a desync.
6539 if (network_get_mode() != NETWORK_MODE_NONE)
6540 {
6541 if (tileElement->IsGhost())
6542 continue;
6543 }
6544 if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
6545 continue;
6546 if (direction_reverse(tileElement->GetDirection()) != edge)
6547 continue;
6548 auto wallEntry = tileElement->AsWall()->GetEntry();
6549 if (wallEntry == nullptr || (wallEntry->flags2 & WALL_SCENERY_2_IS_OPAQUE))
6550 continue;
6551 if (peep->NextLoc.z + (8 * COORDS_Z_STEP) <= tileElement->GetBaseZ())
6552 continue;
6553 if (peep->NextLoc.z >= tileElement->GetClearanceZ())
6554 continue;
6555
6556 return false;
6557 } while (!(tileElement++)->IsLastForTile());
6558
6559 // TODO: Extract loop B
6560 tileElement = reinterpret_cast<TileElement*>(surfaceElement);
6561 do
6562 {
6563 // Ghosts are purely this-client-side and should not cause any interaction,
6564 // as that may lead to a desync.
6565 if (network_get_mode() != NETWORK_MODE_NONE)
6566 {
6567 if (tileElement->IsGhost())
6568 continue;
6569 }
6570 if (tileElement->GetClearanceZ() + (1 * COORDS_Z_STEP) < peep->NextLoc.z)
6571 continue;
6572 if (peep->NextLoc.z + (10 * COORDS_Z_STEP) < tileElement->GetBaseZ())
6573 continue;
6574
6575 if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
6576 {
6577 if (peep_should_watch_ride(tileElement))
6578 {
6579 return loc_690FD0(peep, rideToView, rideSeatToView, tileElement);
6580 }
6581 }
6582
6583 if (tileElement->GetType() == TILE_ELEMENT_TYPE_LARGE_SCENERY)
6584 {
6585 const auto* sceneryEntry = tileElement->AsLargeScenery()->GetEntry();
6586 if (sceneryEntry == nullptr || !(sceneryEntry->flags & LARGE_SCENERY_FLAG_PHOTOGENIC))
6587 {
6588 continue;
6589 }
6590
6591 *rideSeatToView = 0;
6592 if (tileElement->GetClearanceZ() >= peep->NextLoc.z + (8 * COORDS_Z_STEP))
6593 {
6594 *rideSeatToView = 0x02;
6595 }
6596
6597 *rideToView = RIDE_ID_NULL;
6598
6599 return true;
6600 }
6601 } while (!(tileElement++)->IsLastForTile());
6602
6603 return false;
6604 }
6605
6606 /* Part of 0x0069B8CC rct2: 0x0069BC31 */
SetSpriteType(PeepSpriteType new_sprite_type)6607 void Guest::SetSpriteType(PeepSpriteType new_sprite_type)
6608 {
6609 if (SpriteType == new_sprite_type)
6610 return;
6611
6612 SpriteType = new_sprite_type;
6613 ActionSpriteImageOffset = 0;
6614 WalkingFrameNum = 0;
6615
6616 if (IsActionInterruptable())
6617 Action = PeepActionType::Walking;
6618
6619 PeepFlags &= ~PEEP_FLAGS_SLOW_WALK;
6620 Guard::Assert(EnumValue(new_sprite_type) < std::size(gSpriteTypeToSlowWalkMap));
6621 if (gSpriteTypeToSlowWalkMap[EnumValue(new_sprite_type)])
6622 {
6623 PeepFlags |= PEEP_FLAGS_SLOW_WALK;
6624 }
6625
6626 ActionSpriteType = PeepActionSpriteType::Invalid;
6627 UpdateCurrentActionSpriteType();
6628
6629 if (State == PeepState::Sitting)
6630 {
6631 Action = PeepActionType::Idle;
6632 NextActionSpriteType = PeepActionSpriteType::SittingIdle;
6633 SwitchNextActionSpriteType();
6634 }
6635 if (State == PeepState::Watching)
6636 {
6637 Action = PeepActionType::Idle;
6638 NextActionSpriteType = PeepActionSpriteType::WatchRide;
6639 SwitchNextActionSpriteType();
6640 }
6641 }
6642
6643 struct item_pref_t
6644 {
6645 ShopItem item;
6646 PeepSpriteType sprite_type;
6647 };
6648
6649 // clang-format off
6650 static item_pref_t item_order_preference[] = {
6651 { ShopItem::IceCream, PeepSpriteType::IceCream },
6652 { ShopItem::Chips, PeepSpriteType::Chips },
6653 { ShopItem::Pizza, PeepSpriteType::Pizza },
6654 { ShopItem::Burger, PeepSpriteType::Burger },
6655 { ShopItem::Drink, PeepSpriteType::Drink },
6656 { ShopItem::Coffee, PeepSpriteType::Coffee },
6657 { ShopItem::Chicken, PeepSpriteType::Chicken },
6658 { ShopItem::Lemonade, PeepSpriteType::Lemonade },
6659 { ShopItem::Candyfloss, PeepSpriteType::Candyfloss },
6660 { ShopItem::Popcorn, PeepSpriteType::Popcorn },
6661 { ShopItem::HotDog, PeepSpriteType::HotDog },
6662 { ShopItem::Tentacle, PeepSpriteType::Tentacle },
6663 { ShopItem::ToffeeApple, PeepSpriteType::ToffeeApple },
6664 { ShopItem::Doughnut, PeepSpriteType::Doughnut },
6665 { ShopItem::Pretzel, PeepSpriteType::Pretzel },
6666 { ShopItem::Cookie, PeepSpriteType::Pretzel },
6667 { ShopItem::Chocolate, PeepSpriteType::Coffee },
6668 { ShopItem::IcedTea, PeepSpriteType::Coffee },
6669 { ShopItem::FunnelCake, PeepSpriteType::FunnelCake },
6670 { ShopItem::BeefNoodles, PeepSpriteType::Noodles },
6671 { ShopItem::FriedRiceNoodles, PeepSpriteType::Noodles },
6672 { ShopItem::WontonSoup, PeepSpriteType::Soup },
6673 { ShopItem::MeatballSoup, PeepSpriteType::Soup },
6674 { ShopItem::FruitJuice, PeepSpriteType::Juice },
6675 { ShopItem::SoybeanMilk, PeepSpriteType::SuJongkwa },
6676 { ShopItem::Sujeonggwa, PeepSpriteType::SuJongkwa },
6677 { ShopItem::SubSandwich, PeepSpriteType::Sandwich },
6678 { ShopItem::RoastSausage, PeepSpriteType::Sausage },
6679 { ShopItem::Balloon, PeepSpriteType::Balloon },
6680 { ShopItem::Hat, PeepSpriteType::Hat },
6681 { ShopItem::Sunglasses, PeepSpriteType::Sunglasses },
6682 };
6683 // clang-format on
6684
6685 /**
6686 *
6687 * rct2: 0x0069B8CC
6688 */
UpdateSpriteType()6689 void Guest::UpdateSpriteType()
6690 {
6691 if (SpriteType == PeepSpriteType::Balloon && (scenario_rand() & 0xFFFF) <= 327)
6692 {
6693 bool isBalloonPopped = false;
6694 if (x != LOCATION_NULL)
6695 {
6696 if ((scenario_rand() & 0xFFFF) <= 13107)
6697 {
6698 isBalloonPopped = true;
6699 OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::BalloonPop, { x, y, z });
6700 }
6701 Balloon::Create({ x, y, z + 9 }, BalloonColour, isBalloonPopped);
6702 }
6703 RemoveItem(ShopItem::Balloon);
6704 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_INVENTORY;
6705 }
6706
6707 if (climate_is_raining() && (HasItem(ShopItem::Umbrella)) && x != LOCATION_NULL)
6708 {
6709 CoordsXY loc = { x, y };
6710 if (map_is_location_valid(loc.ToTileStart()))
6711 {
6712 TileElement* tileElement = map_get_first_element_at(loc);
6713 while (true)
6714 {
6715 if (tileElement == nullptr)
6716 break;
6717 if (z < tileElement->GetBaseZ())
6718 break;
6719
6720 if (tileElement->IsLastForTile())
6721 {
6722 SetSpriteType(PeepSpriteType::Umbrella);
6723 return;
6724 }
6725 tileElement++;
6726 }
6727 }
6728 }
6729
6730 for (auto& itemPref : item_order_preference)
6731 {
6732 if (HasItem(itemPref.item))
6733 {
6734 SetSpriteType(itemPref.sprite_type);
6735 return;
6736 }
6737 }
6738
6739 if (State == PeepState::Watching && StandingFlags & (1 << 1))
6740 {
6741 SetSpriteType(PeepSpriteType::Watching);
6742 return;
6743 }
6744
6745 if (Nausea > 170)
6746 {
6747 SetSpriteType(PeepSpriteType::VeryNauseous);
6748 return;
6749 }
6750
6751 if (Nausea > 140)
6752 {
6753 SetSpriteType(PeepSpriteType::Nauseous);
6754 return;
6755 }
6756
6757 if (Energy <= 64 && Happiness < 128)
6758 {
6759 SetSpriteType(PeepSpriteType::HeadDown);
6760 return;
6761 }
6762
6763 if (Energy <= 80 && Happiness < 128)
6764 {
6765 SetSpriteType(PeepSpriteType::ArmsCrossed);
6766 return;
6767 }
6768
6769 if (Toilet > 220)
6770 {
6771 SetSpriteType(PeepSpriteType::RequireToilet);
6772 return;
6773 }
6774
6775 SetSpriteType(PeepSpriteType::Normal);
6776 }
6777
HeadingForRideOrParkExit() const6778 bool Guest::HeadingForRideOrParkExit() const
6779 {
6780 return (PeepFlags & PEEP_FLAGS_LEAVING_PARK) || (GuestHeadingToRideId != RIDE_ID_NULL);
6781 }
6782
6783 /**
6784 * rct2: 0x00698342
6785 * thought.item (eax)
6786 * thought.type (ebx)
6787 * argument_1 (esi & ebx)
6788 * argument_2 (esi+2)
6789 */
peep_thought_set_format_args(const PeepThought * thought,Formatter & ft)6790 void peep_thought_set_format_args(const PeepThought* thought, Formatter& ft)
6791 {
6792 ft.Add<rct_string_id>(PeepThoughts[EnumValue(thought->type)]);
6793
6794 PeepThoughtToActionFlag flags = PeepThoughtToActionMap[EnumValue(thought->type)].flags;
6795 if (flags & PEEP_THOUGHT_ACTION_FLAG_RIDE)
6796 {
6797 auto ride = get_ride(thought->rideId);
6798 if (ride != nullptr)
6799 {
6800 ride->FormatNameTo(ft);
6801 }
6802 else
6803 {
6804 ft.Add<rct_string_id>(STR_NONE);
6805 }
6806 }
6807 else if (flags & PEEP_THOUGHT_ACTION_FLAG_SHOP_ITEM_SINGULAR)
6808 {
6809 ft.Add<rct_string_id>(GetShopItemDescriptor(thought->shopItem).Naming.Singular);
6810 }
6811 else if (flags & PEEP_THOUGHT_ACTION_FLAG_SHOP_ITEM_INDEFINITE)
6812 {
6813 ft.Add<rct_string_id>(GetShopItemDescriptor(thought->shopItem).Naming.Indefinite);
6814 }
6815 }
6816
InsertNewThought(PeepThoughtType thought_type)6817 void Guest::InsertNewThought(PeepThoughtType thought_type)
6818 {
6819 InsertNewThought(thought_type, PeepThoughtItemNone);
6820 }
6821
InsertNewThought(PeepThoughtType thought_type,ShopItem shopItem)6822 void Guest::InsertNewThought(PeepThoughtType thought_type, ShopItem shopItem)
6823 {
6824 InsertNewThought(thought_type, static_cast<uint16_t>(shopItem));
6825 }
6826
InsertNewThought(PeepThoughtType thought_type,ride_id_t rideId)6827 void Guest::InsertNewThought(PeepThoughtType thought_type, ride_id_t rideId)
6828 {
6829 InsertNewThought(thought_type, static_cast<uint16_t>(rideId));
6830 }
6831 /**
6832 *
6833 * rct2: 0x699F5A
6834 * al:thoughtType
6835 * ah:thoughtArguments
6836 * esi: peep
6837 */
InsertNewThought(PeepThoughtType thoughtType,uint16_t thoughtArguments)6838 void Guest::InsertNewThought(PeepThoughtType thoughtType, uint16_t thoughtArguments)
6839 {
6840 PeepActionType newAction = PeepThoughtToActionMap[EnumValue(thoughtType)].action;
6841 if (newAction != PeepActionType::Walking && IsActionInterruptable())
6842 {
6843 Action = newAction;
6844 ActionFrame = 0;
6845 ActionSpriteImageOffset = 0;
6846 UpdateCurrentActionSpriteType();
6847 }
6848
6849 for (int32_t i = 0; i < PEEP_MAX_THOUGHTS; ++i)
6850 {
6851 PeepThought* thought = &Thoughts[i];
6852 // Remove the oldest thought by setting it to NONE.
6853 if (thought->type == PeepThoughtType::None)
6854 break;
6855
6856 if (thought->type == thoughtType && thought->item == thoughtArguments)
6857 {
6858 // If the thought type has not changed then we need to move
6859 // it to the top of the thought list. This is done by first removing the
6860 // existing thought and placing it at the top.
6861 if (i < PEEP_MAX_THOUGHTS - 2)
6862 {
6863 memmove(thought, thought + 1, sizeof(PeepThought) * (PEEP_MAX_THOUGHTS - i - 1));
6864 }
6865 break;
6866 }
6867 }
6868
6869 memmove(&Thoughts[1], &Thoughts[0], sizeof(PeepThought) * (PEEP_MAX_THOUGHTS - 1));
6870
6871 Thoughts[0].type = thoughtType;
6872 Thoughts[0].item = thoughtArguments;
6873 Thoughts[0].freshness = 0;
6874 Thoughts[0].fresh_timeout = 0;
6875
6876 WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_THOUGHTS;
6877 }
6878
6879 // clang-format off
6880 /** rct2: 0x009823A0 */
6881 static constexpr const PeepNauseaTolerance nausea_tolerance_distribution[] = {
6882 PeepNauseaTolerance::None,
6883 PeepNauseaTolerance::Low, PeepNauseaTolerance::Low,
6884 PeepNauseaTolerance::Average, PeepNauseaTolerance::Average, PeepNauseaTolerance::Average,
6885 PeepNauseaTolerance::High, PeepNauseaTolerance::High, PeepNauseaTolerance::High, PeepNauseaTolerance::High, PeepNauseaTolerance::High, PeepNauseaTolerance::High,
6886 };
6887
6888 /** rct2: 0x009823BC */
6889 static constexpr const uint8_t trouser_colours[] = {
6890 COLOUR_BLACK,
6891 COLOUR_GREY,
6892 COLOUR_LIGHT_BROWN,
6893 COLOUR_SATURATED_BROWN,
6894 COLOUR_DARK_BROWN,
6895 COLOUR_SALMON_PINK,
6896 COLOUR_BLACK,
6897 COLOUR_GREY,
6898 COLOUR_LIGHT_BROWN,
6899 COLOUR_SATURATED_BROWN,
6900 COLOUR_DARK_BROWN,
6901 COLOUR_SALMON_PINK,
6902 COLOUR_BLACK,
6903 COLOUR_GREY,
6904 COLOUR_LIGHT_BROWN,
6905 COLOUR_SATURATED_BROWN,
6906 COLOUR_DARK_BROWN,
6907 COLOUR_SALMON_PINK,
6908 COLOUR_DARK_PURPLE,
6909 COLOUR_LIGHT_PURPLE,
6910 COLOUR_DARK_BLUE,
6911 COLOUR_SATURATED_GREEN,
6912 COLOUR_SATURATED_RED,
6913 COLOUR_DARK_ORANGE,
6914 COLOUR_BORDEAUX_RED,
6915 };
6916
6917 /** rct2: 0x009823D5 */
6918 static constexpr const uint8_t tshirt_colours[] = {
6919 COLOUR_BLACK,
6920 COLOUR_GREY,
6921 COLOUR_LIGHT_BROWN,
6922 COLOUR_SATURATED_BROWN,
6923 COLOUR_DARK_BROWN,
6924 COLOUR_SALMON_PINK,
6925 COLOUR_BLACK,
6926 COLOUR_GREY,
6927 COLOUR_LIGHT_BROWN,
6928 COLOUR_SATURATED_BROWN,
6929 COLOUR_DARK_BROWN,
6930 COLOUR_SALMON_PINK,
6931 COLOUR_DARK_PURPLE,
6932 COLOUR_LIGHT_PURPLE,
6933 COLOUR_DARK_BLUE,
6934 COLOUR_SATURATED_GREEN,
6935 COLOUR_SATURATED_RED,
6936 COLOUR_DARK_ORANGE,
6937 COLOUR_BORDEAUX_RED,
6938 COLOUR_WHITE,
6939 COLOUR_BRIGHT_PURPLE,
6940 COLOUR_LIGHT_BLUE,
6941 COLOUR_TEAL,
6942 COLOUR_DARK_GREEN,
6943 COLOUR_MOSS_GREEN,
6944 COLOUR_BRIGHT_GREEN,
6945 COLOUR_OLIVE_GREEN,
6946 COLOUR_DARK_OLIVE_GREEN,
6947 COLOUR_YELLOW,
6948 COLOUR_LIGHT_ORANGE,
6949 COLOUR_BRIGHT_RED,
6950 COLOUR_DARK_PINK,
6951 COLOUR_BRIGHT_PINK,
6952 };
6953 // clang-format on
6954
6955 /**
6956 *
6957 * rct2: 0x0069A05D
6958 */
Generate(const CoordsXYZ & coords)6959 Guest* Guest::Generate(const CoordsXYZ& coords)
6960 {
6961 if (GetNumFreeEntities() < 400)
6962 return nullptr;
6963
6964 Guest* peep = CreateEntity<Guest>();
6965 peep->SpriteType = PeepSpriteType::Normal;
6966 peep->OutsideOfPark = true;
6967 peep->State = PeepState::Falling;
6968 peep->Action = PeepActionType::Walking;
6969 peep->SpecialSprite = 0;
6970 peep->ActionSpriteImageOffset = 0;
6971 peep->WalkingFrameNum = 0;
6972 peep->ActionSpriteType = PeepActionSpriteType::None;
6973 peep->PeepFlags = 0;
6974 peep->FavouriteRide = RIDE_ID_NULL;
6975 peep->FavouriteRideRating = 0;
6976
6977 const rct_sprite_bounds* spriteBounds = &GetSpriteBounds(peep->SpriteType, peep->ActionSpriteType);
6978 peep->sprite_width = spriteBounds->sprite_width;
6979 peep->sprite_height_negative = spriteBounds->sprite_height_negative;
6980 peep->sprite_height_positive = spriteBounds->sprite_height_positive;
6981
6982 peep->MoveTo(coords);
6983 peep->sprite_direction = 0;
6984 peep->Mass = (scenario_rand() & 0x1F) + 45;
6985 peep->PathCheckOptimisation = 0;
6986 peep->InteractionRideIndex = RIDE_ID_NULL;
6987 peep->PreviousRide = RIDE_ID_NULL;
6988 peep->Thoughts[0].type = PeepThoughtType::None;
6989 peep->WindowInvalidateFlags = 0;
6990
6991 uint8_t intensityHighest = (scenario_rand() & 0x7) + 3;
6992 uint8_t intensityLowest = std::min(intensityHighest, static_cast<uint8_t>(7)) - 3;
6993
6994 if (intensityHighest >= 7)
6995 intensityHighest = 15;
6996
6997 /* Check which intensity boxes are enabled
6998 * and apply the appropriate intensity settings. */
6999 if (gParkFlags & PARK_FLAGS_PREF_LESS_INTENSE_RIDES)
7000 {
7001 if (gParkFlags & PARK_FLAGS_PREF_MORE_INTENSE_RIDES)
7002 {
7003 intensityLowest = 0;
7004 intensityHighest = 15;
7005 }
7006 else
7007 {
7008 intensityLowest = 0;
7009 intensityHighest = 4;
7010 }
7011 }
7012 else if (gParkFlags & PARK_FLAGS_PREF_MORE_INTENSE_RIDES)
7013 {
7014 intensityLowest = 9;
7015 intensityHighest = 15;
7016 }
7017
7018 peep->Intensity = IntensityRange(intensityLowest, intensityHighest);
7019
7020 uint8_t nauseaTolerance = scenario_rand() & 0x7;
7021 if (gParkFlags & PARK_FLAGS_PREF_MORE_INTENSE_RIDES)
7022 {
7023 nauseaTolerance += 4;
7024 }
7025
7026 peep->NauseaTolerance = nausea_tolerance_distribution[nauseaTolerance];
7027
7028 /* Scenario editor limits initial guest happiness to between 37..253.
7029 * To be on the safe side, assume the value could have been hacked
7030 * to any value 0..255. */
7031 peep->Happiness = gGuestInitialHappiness;
7032 /* Assume a default initial happiness of 0 is wrong and set
7033 * to 128 (50%) instead. */
7034 if (gGuestInitialHappiness == 0)
7035 peep->Happiness = 128;
7036 /* Initial value will vary by -15..16 */
7037 int8_t happinessDelta = (scenario_rand() & 0x1F) - 15;
7038 /* Adjust by the delta, clamping at min=0 and max=255. */
7039 peep->Happiness = std::clamp(peep->Happiness + happinessDelta, 0, PEEP_MAX_HAPPINESS);
7040 peep->HappinessTarget = peep->Happiness;
7041 peep->Nausea = 0;
7042 peep->NauseaTarget = 0;
7043
7044 /* Scenario editor limits initial guest hunger to between 37..253.
7045 * To be on the safe side, assume the value could have been hacked
7046 * to any value 0..255. */
7047 peep->Hunger = gGuestInitialHunger;
7048 /* Initial value will vary by -15..16 */
7049 int8_t hungerDelta = (scenario_rand() & 0x1F) - 15;
7050 /* Adjust by the delta, clamping at min=0 and max=255. */
7051 peep->Hunger = std::clamp(peep->Hunger + hungerDelta, 0, PEEP_MAX_HUNGER);
7052
7053 /* Scenario editor limits initial guest thirst to between 37..253.
7054 * To be on the safe side, assume the value could have been hacked
7055 * to any value 0..255. */
7056 peep->Thirst = gGuestInitialThirst;
7057 /* Initial value will vary by -15..16 */
7058 int8_t thirstDelta = (scenario_rand() & 0x1F) - 15;
7059 /* Adjust by the delta, clamping at min=0 and max=255. */
7060 peep->Thirst = std::clamp(peep->Thirst + thirstDelta, 0, PEEP_MAX_THIRST);
7061
7062 peep->Toilet = 0;
7063 peep->TimeToConsume = 0;
7064
7065 peep->GuestNumRides = 0;
7066 peep->Id = gNextGuestNumber++;
7067 peep->Name = nullptr;
7068
7069 money32 cash = (scenario_rand() & 0x3) * 100 - 100 + gGuestInitialCash;
7070 if (cash < 0)
7071 cash = 0;
7072
7073 if (gGuestInitialCash == 0)
7074 {
7075 cash = 500;
7076 }
7077
7078 if (gParkFlags & PARK_FLAGS_NO_MONEY)
7079 {
7080 cash = 0;
7081 }
7082
7083 if (gGuestInitialCash == MONEY16_UNDEFINED)
7084 {
7085 cash = 0;
7086 }
7087
7088 peep->CashInPocket = cash;
7089 peep->CashSpent = 0;
7090 peep->ParkEntryTime = -1;
7091 peep->ResetPathfindGoal();
7092 peep->RemoveAllItems();
7093 peep->GuestHeadingToRideId = RIDE_ID_NULL;
7094 peep->LitterCount = 0;
7095 peep->DisgustingCount = 0;
7096 peep->VandalismSeen = 0;
7097 peep->PaidToEnter = 0;
7098 peep->PaidOnRides = 0;
7099 peep->PaidOnFood = 0;
7100 peep->PaidOnDrink = 0;
7101 peep->PaidOnSouvenirs = 0;
7102 peep->AmountOfFood = 0;
7103 peep->AmountOfDrinks = 0;
7104 peep->AmountOfSouvenirs = 0;
7105 peep->SurroundingsThoughtTimeout = 0;
7106 peep->Angriness = 0;
7107 peep->TimeLost = 0;
7108
7109 uint8_t tshirtColour = static_cast<uint8_t>(scenario_rand() % std::size(tshirt_colours));
7110 peep->TshirtColour = tshirt_colours[tshirtColour];
7111
7112 uint8_t trousersColour = static_cast<uint8_t>(scenario_rand() % std::size(trouser_colours));
7113 peep->TrousersColour = trouser_colours[trousersColour];
7114
7115 /* Minimum energy is capped at 32 and maximum at 128, so this initialises
7116 * a peep with approx 34%-100% energy. (65 - 32) / (128 - 32) ≈ 34% */
7117 uint8_t energy = (scenario_rand() % 64) + 65;
7118 peep->Energy = energy;
7119 peep->EnergyTarget = energy;
7120
7121 increment_guests_heading_for_park();
7122
7123 #ifdef ENABLE_SCRIPTING
7124 auto& hookEngine = OpenRCT2::GetContext()->GetScriptEngine().GetHookEngine();
7125 if (hookEngine.HasSubscriptions(OpenRCT2::Scripting::HOOK_TYPE::GUEST_GENERATION))
7126 {
7127 auto ctx = OpenRCT2::GetContext()->GetScriptEngine().GetContext();
7128
7129 // Create event args object
7130 auto obj = OpenRCT2::Scripting::DukObject(ctx);
7131 obj.Set("id", peep->sprite_index);
7132
7133 // Call the subscriptions
7134 auto e = obj.Take();
7135 hookEngine.Call(OpenRCT2::Scripting::HOOK_TYPE::GUEST_GENERATION, e, true);
7136 }
7137 #endif
7138
7139 return peep;
7140 }
7141
7142 enum
7143 {
7144 PEEP_FACE_OFFSET_ANGRY = 0,
7145 PEEP_FACE_OFFSET_VERY_VERY_SICK,
7146 PEEP_FACE_OFFSET_VERY_SICK,
7147 PEEP_FACE_OFFSET_SICK,
7148 PEEP_FACE_OFFSET_VERY_TIRED,
7149 PEEP_FACE_OFFSET_TIRED,
7150 PEEP_FACE_OFFSET_VERY_VERY_UNHAPPY,
7151 PEEP_FACE_OFFSET_VERY_UNHAPPY,
7152 PEEP_FACE_OFFSET_UNHAPPY,
7153 PEEP_FACE_OFFSET_NORMAL,
7154 PEEP_FACE_OFFSET_HAPPY,
7155 PEEP_FACE_OFFSET_VERY_HAPPY,
7156 PEEP_FACE_OFFSET_VERY_VERY_HAPPY,
7157 };
7158
7159 static constexpr const int32_t face_sprite_small[] = {
7160 SPR_PEEP_SMALL_FACE_ANGRY,
7161 SPR_PEEP_SMALL_FACE_VERY_VERY_SICK,
7162 SPR_PEEP_SMALL_FACE_VERY_SICK,
7163 SPR_PEEP_SMALL_FACE_SICK,
7164 SPR_PEEP_SMALL_FACE_VERY_TIRED,
7165 SPR_PEEP_SMALL_FACE_TIRED,
7166 SPR_PEEP_SMALL_FACE_VERY_VERY_UNHAPPY,
7167 SPR_PEEP_SMALL_FACE_VERY_UNHAPPY,
7168 SPR_PEEP_SMALL_FACE_UNHAPPY,
7169 SPR_PEEP_SMALL_FACE_NORMAL,
7170 SPR_PEEP_SMALL_FACE_HAPPY,
7171 SPR_PEEP_SMALL_FACE_VERY_HAPPY,
7172 SPR_PEEP_SMALL_FACE_VERY_VERY_HAPPY,
7173 };
7174
7175 static constexpr const int32_t face_sprite_large[] = {
7176 SPR_PEEP_LARGE_FACE_ANGRY_0,
7177 SPR_PEEP_LARGE_FACE_VERY_VERY_SICK_0,
7178 SPR_PEEP_LARGE_FACE_VERY_SICK_0,
7179 SPR_PEEP_LARGE_FACE_SICK,
7180 SPR_PEEP_LARGE_FACE_VERY_TIRED,
7181 SPR_PEEP_LARGE_FACE_TIRED,
7182 SPR_PEEP_LARGE_FACE_VERY_VERY_UNHAPPY,
7183 SPR_PEEP_LARGE_FACE_VERY_UNHAPPY,
7184 SPR_PEEP_LARGE_FACE_UNHAPPY,
7185 SPR_PEEP_LARGE_FACE_NORMAL,
7186 SPR_PEEP_LARGE_FACE_HAPPY,
7187 SPR_PEEP_LARGE_FACE_VERY_HAPPY,
7188 SPR_PEEP_LARGE_FACE_VERY_VERY_HAPPY,
7189 };
7190
get_face_sprite_offset(Guest * peep)7191 static int32_t get_face_sprite_offset(Guest* peep)
7192 {
7193 // ANGRY
7194 if (peep->Angriness > 0)
7195 return PEEP_FACE_OFFSET_ANGRY;
7196
7197 // VERY_VERY_SICK
7198 if (peep->Nausea > 200)
7199 return PEEP_FACE_OFFSET_VERY_VERY_SICK;
7200
7201 // VERY_SICK
7202 if (peep->Nausea > 170)
7203 return PEEP_FACE_OFFSET_VERY_SICK;
7204
7205 // SICK
7206 if (peep->Nausea > 140)
7207 return PEEP_FACE_OFFSET_SICK;
7208
7209 // VERY_TIRED
7210 if (peep->Energy < 46)
7211 return PEEP_FACE_OFFSET_VERY_TIRED;
7212
7213 // TIRED
7214 if (peep->Energy < 70)
7215 return PEEP_FACE_OFFSET_TIRED;
7216
7217 int32_t offset = PEEP_FACE_OFFSET_VERY_VERY_UNHAPPY;
7218 // There are 7 different happiness based faces
7219 for (int32_t i = 37; peep->Happiness >= i; i += 37)
7220 {
7221 offset++;
7222 }
7223
7224 return offset;
7225 }
7226
7227 /**
7228 * Function split into large and small sprite
7229 * rct2: 0x00698721
7230 */
get_peep_face_sprite_small(Guest * peep)7231 int32_t get_peep_face_sprite_small(Guest* peep)
7232 {
7233 return face_sprite_small[get_face_sprite_offset(peep)];
7234 }
7235
7236 /**
7237 * Function split into large and small sprite
7238 * rct2: 0x00698721
7239 */
get_peep_face_sprite_large(Guest * peep)7240 int32_t get_peep_face_sprite_large(Guest* peep)
7241 {
7242 return face_sprite_large[get_face_sprite_offset(peep)];
7243 }
7244
7245 /**
7246 *
7247 * rct2: 0x00693CBB
7248 */
UpdateQueuePosition(PeepActionType previous_action)7249 bool Guest::UpdateQueuePosition(PeepActionType previous_action)
7250 {
7251 TimeInQueue++;
7252
7253 auto* guestNext = GetEntity<Guest>(GuestNextInQueue);
7254 if (guestNext == nullptr)
7255 {
7256 return false;
7257 }
7258
7259 int16_t x_diff = abs(guestNext->x - x);
7260 int16_t y_diff = abs(guestNext->y - y);
7261 int16_t z_diff = abs(guestNext->z - z);
7262
7263 if (z_diff > 10)
7264 return false;
7265
7266 if (x_diff < y_diff)
7267 {
7268 int16_t temp_x = x_diff;
7269 x_diff = y_diff;
7270 y_diff = temp_x;
7271 }
7272
7273 x_diff += y_diff / 2;
7274 if (x_diff > 7)
7275 {
7276 if (x_diff > 13)
7277 {
7278 if ((x & 0xFFE0) != (guestNext->x & 0xFFE0) || (y & 0xFFE0) != (guestNext->y & 0xFFE0))
7279 return false;
7280 }
7281
7282 if (sprite_direction != guestNext->sprite_direction)
7283 return false;
7284
7285 switch (guestNext->sprite_direction / 8)
7286 {
7287 case 0:
7288 if (x >= guestNext->x)
7289 return false;
7290 break;
7291 case 1:
7292 if (y <= guestNext->y)
7293 return false;
7294 break;
7295 case 2:
7296 if (x <= guestNext->x)
7297 return false;
7298 break;
7299 case 3:
7300 if (y >= guestNext->y)
7301 return false;
7302 break;
7303 }
7304 }
7305
7306 if (!IsActionInterruptable())
7307 UpdateAction();
7308
7309 if (!IsActionWalking())
7310 return true;
7311
7312 Action = PeepActionType::Idle;
7313 NextActionSpriteType = PeepActionSpriteType::WatchRide;
7314 if (previous_action != PeepActionType::Idle)
7315 Invalidate();
7316 return true;
7317 }
7318
7319 /**
7320 *
7321 * rct2: 0x006966A9
7322 */
RemoveFromQueue()7323 void Guest::RemoveFromQueue()
7324 {
7325 auto ride = get_ride(CurrentRide);
7326 if (ride == nullptr)
7327 return;
7328
7329 auto& station = ride->stations[CurrentRideStation];
7330 // Make sure we don't underflow, building while paused might reset it to 0 where peeps have
7331 // not yet left the queue.
7332 if (station.QueueLength > 0)
7333 {
7334 station.QueueLength--;
7335 }
7336
7337 if (sprite_index == station.LastPeepInQueue)
7338 {
7339 station.LastPeepInQueue = GuestNextInQueue;
7340 return;
7341 }
7342
7343 auto* otherGuest = GetEntity<Guest>(station.LastPeepInQueue);
7344 if (otherGuest == nullptr)
7345 {
7346 log_error("Invalid Guest Queue list!");
7347 return;
7348 }
7349 for (; otherGuest != nullptr; otherGuest = GetEntity<Guest>(otherGuest->GuestNextInQueue))
7350 {
7351 if (sprite_index == otherGuest->GuestNextInQueue)
7352 {
7353 otherGuest->GuestNextInQueue = GuestNextInQueue;
7354 return;
7355 }
7356 }
7357 }
7358
GetItemFlags() const7359 uint64_t Guest::GetItemFlags() const
7360 {
7361 return ItemFlags;
7362 }
7363
SetItemFlags(uint64_t itemFlags)7364 void Guest::SetItemFlags(uint64_t itemFlags)
7365 {
7366 ItemFlags = itemFlags;
7367 }
7368
RemoveAllItems()7369 void Guest::RemoveAllItems()
7370 {
7371 ItemFlags = 0;
7372 }
7373
RemoveItem(ShopItem item)7374 void Guest::RemoveItem(ShopItem item)
7375 {
7376 ItemFlags &= ~EnumToFlag(item);
7377 }
7378
GiveItem(ShopItem item)7379 void Guest::GiveItem(ShopItem item)
7380 {
7381 ItemFlags |= EnumToFlag(item);
7382 }
7383
HasItem(ShopItem peepItem) const7384 bool Guest::HasItem(ShopItem peepItem) const
7385 {
7386 return GetItemFlags() & EnumToFlag(peepItem);
7387 }
7388
IsThoughtShopItemRelated(const PeepThoughtType type)7389 static bool IsThoughtShopItemRelated(const PeepThoughtType type)
7390 {
7391 switch (type)
7392 {
7393 case PeepThoughtType::AlreadyGot:
7394 case PeepThoughtType::HaventFinished:
7395 case PeepThoughtType::CantAffordItem:
7396 return true;
7397 default:
7398 break;
7399 }
7400 return false;
7401 }
7402
RemoveRideFromMemory(ride_id_t rideId)7403 void Guest::RemoveRideFromMemory(ride_id_t rideId)
7404 {
7405 if (State == PeepState::Watching)
7406 {
7407 if (CurrentRide == rideId)
7408 {
7409 CurrentRide = RIDE_ID_NULL;
7410 if (TimeToStand >= 50)
7411 {
7412 // make peep stop watching the ride
7413 TimeToStand = 50;
7414 }
7415 }
7416 }
7417
7418 // remove any free voucher for this ride from peep
7419 if (HasItem(ShopItem::Voucher))
7420 {
7421 if (VoucherType == VOUCHER_TYPE_RIDE_FREE && VoucherRideId == rideId)
7422 {
7423 RemoveItem(ShopItem::Voucher);
7424 }
7425 }
7426
7427 // remove any photos of this ride from peep
7428 if (HasItem(ShopItem::Photo))
7429 {
7430 if (Photo1RideRef == rideId)
7431 {
7432 RemoveItem(ShopItem::Photo);
7433 }
7434 }
7435 if (HasItem(ShopItem::Photo2))
7436 {
7437 if (Photo2RideRef == rideId)
7438 {
7439 RemoveItem(ShopItem::Photo2);
7440 }
7441 }
7442 if (HasItem(ShopItem::Photo3))
7443 {
7444 if (Photo3RideRef == rideId)
7445 {
7446 RemoveItem(ShopItem::Photo3);
7447 }
7448 }
7449 if (HasItem(ShopItem::Photo4))
7450 {
7451 if (Photo4RideRef == rideId)
7452 {
7453 RemoveItem(ShopItem::Photo4);
7454 }
7455 }
7456
7457 if (GuestHeadingToRideId == rideId)
7458 {
7459 GuestHeadingToRideId = RIDE_ID_NULL;
7460 }
7461 if (FavouriteRide == rideId)
7462 {
7463 FavouriteRide = RIDE_ID_NULL;
7464 }
7465
7466 // Erase all thoughts that contain the ride.
7467 for (auto it = std::begin(Thoughts); it != std::end(Thoughts);)
7468 {
7469 const auto& entry = *it;
7470 if (entry.type == PeepThoughtType::None)
7471 break;
7472
7473 // Ride ids and shop item ids might have the same value, look only for ride thoughts.
7474 if (IsThoughtShopItemRelated(entry.type) || entry.rideId != rideId)
7475 {
7476 it++;
7477 continue;
7478 }
7479
7480 if (auto itNext = std::next(it); itNext != std::end(Thoughts))
7481 {
7482 // Overwrite this entry by shifting all entries that follow.
7483 std::rotate(it, itNext, std::end(Thoughts));
7484 }
7485
7486 // Last slot is now free.
7487 auto& lastEntry = Thoughts.back();
7488 lastEntry.type = PeepThoughtType::None;
7489 lastEntry.item = PeepThoughtItemNone;
7490 }
7491 }
7492