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