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 "Peep.h"
11 
12 #include "../Cheats.h"
13 #include "../Context.h"
14 #include "../Game.h"
15 #include "../Input.h"
16 #include "../OpenRCT2.h"
17 #include "../actions/GameAction.h"
18 #include "../audio/AudioMixer.h"
19 #include "../audio/audio.h"
20 #include "../config/Config.h"
21 #include "../core/Guard.hpp"
22 #include "../interface/Window.h"
23 #include "../localisation/Localisation.h"
24 #include "../management/Finance.h"
25 #include "../management/Marketing.h"
26 #include "../management/NewsItem.h"
27 #include "../network/network.h"
28 #include "../ride/Ride.h"
29 #include "../ride/RideData.h"
30 #include "../ride/ShopItem.h"
31 #include "../ride/Station.h"
32 #include "../ride/Track.h"
33 #include "../scenario/Scenario.h"
34 #include "../sprites.h"
35 #include "../util/Util.h"
36 #include "../windows/Intent.h"
37 #include "../world/Balloon.h"
38 #include "../world/Climate.h"
39 #include "../world/ConstructionClearance.h"
40 #include "../world/EntityTweener.h"
41 #include "../world/Entrance.h"
42 #include "../world/Footpath.h"
43 #include "../world/LargeScenery.h"
44 #include "../world/Map.h"
45 #include "../world/Park.h"
46 #include "../world/Scenery.h"
47 #include "../world/SmallScenery.h"
48 #include "../world/Sprite.h"
49 #include "../world/Surface.h"
50 #include "GuestPathfinding.h"
51 #include "Staff.h"
52 
53 #include <algorithm>
54 #include <iterator>
55 #include <limits>
56 
57 uint8_t gGuestChangeModifier;
58 uint32_t gNumGuestsInPark;
59 uint32_t gNumGuestsInParkLastWeek;
60 uint32_t gNumGuestsHeadingForPark;
61 
62 money16 gGuestInitialCash;
63 uint8_t gGuestInitialHappiness;
64 uint8_t gGuestInitialHunger;
65 uint8_t gGuestInitialThirst;
66 
67 uint32_t gNextGuestNumber;
68 
69 uint8_t gPeepWarningThrottle[16];
70 
71 static uint8_t _unk_F1AEF0;
72 static TileElement* _peepRideEntranceExitElement;
73 
74 static void* _crowdSoundChannel = nullptr;
75 
76 static void peep_128_tick_update(Peep* peep, int32_t index);
77 static void peep_release_balloon(Guest* peep, int16_t spawn_height);
78 
79 static PeepActionSpriteType PeepSpecialSpriteToSpriteTypeMap[] = {
80     PeepActionSpriteType::None,
81     PeepActionSpriteType::HoldMat,
82     PeepActionSpriteType::StaffMower,
83 };
84 
85 static PeepActionSpriteType PeepActionToSpriteTypeMap[] = {
86     PeepActionSpriteType::CheckTime,
87     PeepActionSpriteType::EatFood,
88     PeepActionSpriteType::ShakeHead,
89     PeepActionSpriteType::EmptyPockets,
90     PeepActionSpriteType::SittingEatFood,
91     PeepActionSpriteType::SittingLookAroundLeft,
92     PeepActionSpriteType::SittingLookAroundRight,
93     PeepActionSpriteType::Wow,
94     PeepActionSpriteType::ThrowUp,
95     PeepActionSpriteType::Jump,
96     PeepActionSpriteType::StaffSweep,
97     PeepActionSpriteType::Drowning,
98     PeepActionSpriteType::StaffAnswerCall,
99     PeepActionSpriteType::StaffAnswerCall2,
100     PeepActionSpriteType::StaffCheckboard,
101     PeepActionSpriteType::StaffFix,
102     PeepActionSpriteType::StaffFix2,
103     PeepActionSpriteType::StaffFixGround,
104     PeepActionSpriteType::StaffFix3,
105     PeepActionSpriteType::StaffWatering,
106     PeepActionSpriteType::Joy,
107     PeepActionSpriteType::ReadMap,
108     PeepActionSpriteType::Wave,
109     PeepActionSpriteType::StaffEmptyBin,
110     PeepActionSpriteType::Wave2,
111     PeepActionSpriteType::TakePhoto,
112     PeepActionSpriteType::Clap,
113     PeepActionSpriteType::Disgust,
114     PeepActionSpriteType::DrawPicture,
115     PeepActionSpriteType::BeingWatched,
116     PeepActionSpriteType::WithdrawMoney,
117 };
118 
119 const bool gSpriteTypeToSlowWalkMap[] = {
120     false, false, false, false, false, false, false, false, false, false, false, true, false, false, true,  true,
121     true,  true,  true,  false, true,  false, true,  true,  true,  false, false, true, true,  false, false, true,
122     true,  true,  true,  true,  true,  true,  false, true,  false, true,  true,  true, true,  true,  true,  true,
123 };
124 
Is() const125 template<> bool EntityBase::Is<Peep>() const
126 {
127     return Type == EntityType::Guest || Type == EntityType::Staff;
128 }
129 
GetNextDirection() const130 uint8_t Peep::GetNextDirection() const
131 {
132     return NextFlags & PEEP_NEXT_FLAG_DIRECTION_MASK;
133 }
134 
GetNextIsSloped() const135 bool Peep::GetNextIsSloped() const
136 {
137     return NextFlags & PEEP_NEXT_FLAG_IS_SLOPED;
138 }
139 
GetNextIsSurface() const140 bool Peep::GetNextIsSurface() const
141 {
142     return NextFlags & PEEP_NEXT_FLAG_IS_SURFACE;
143 }
144 
SetNextFlags(uint8_t next_direction,bool is_sloped,bool is_surface)145 void Peep::SetNextFlags(uint8_t next_direction, bool is_sloped, bool is_surface)
146 {
147     NextFlags = next_direction & PEEP_NEXT_FLAG_DIRECTION_MASK;
148     NextFlags |= is_sloped ? PEEP_NEXT_FLAG_IS_SLOPED : 0;
149     NextFlags |= is_surface ? PEEP_NEXT_FLAG_IS_SURFACE : 0;
150 }
151 
CanBePickedUp() const152 bool Peep::CanBePickedUp() const
153 {
154     switch (State)
155     {
156         case PeepState::One:
157         case PeepState::QueuingFront:
158         case PeepState::OnRide:
159         case PeepState::EnteringRide:
160         case PeepState::LeavingRide:
161         case PeepState::EnteringPark:
162         case PeepState::LeavingPark:
163         case PeepState::Fixing:
164         case PeepState::Buying:
165         case PeepState::Inspecting:
166             return false;
167         case PeepState::Falling:
168         case PeepState::Walking:
169         case PeepState::Queuing:
170         case PeepState::Sitting:
171         case PeepState::Picked:
172         case PeepState::Patrolling:
173         case PeepState::Mowing:
174         case PeepState::Sweeping:
175         case PeepState::Answering:
176         case PeepState::Watching:
177         case PeepState::EmptyingBin:
178         case PeepState::UsingBin:
179         case PeepState::Watering:
180         case PeepState::HeadingToInspection:
181             return true;
182     }
183     return false;
184 }
185 
peep_get_staff_count()186 int32_t peep_get_staff_count()
187 {
188     return GetEntityListCount(EntityType::Staff);
189 }
190 
191 /**
192  *
193  *  rct2: 0x0068F0A9
194  */
peep_update_all()195 void peep_update_all()
196 {
197     if (gScreenFlags & SCREEN_FLAGS_EDITOR)
198         return;
199 
200     int32_t i = 0;
201     // Warning this loop can delete peeps
202     for (auto peep : EntityList<Guest>())
203     {
204         if (static_cast<uint32_t>(i & 0x7F) != (gCurrentTicks & 0x7F))
205         {
206             peep->Update();
207         }
208         else
209         {
210             peep_128_tick_update(peep, i);
211             // 128 tick can delete so double check its not deleted
212             if (peep->Type == EntityType::Guest)
213             {
214                 peep->Update();
215             }
216         }
217 
218         i++;
219     }
220 
221     for (auto staff : EntityList<Staff>())
222     {
223         if (static_cast<uint32_t>(i & 0x7F) != (gCurrentTicks & 0x7F))
224         {
225             staff->Update();
226         }
227         else
228         {
229             peep_128_tick_update(staff, i);
230             // 128 tick can delete so double check its not deleted
231             if (staff->Type == EntityType::Staff)
232             {
233                 staff->Update();
234             }
235         }
236 
237         i++;
238     }
239 }
240 
241 /**
242  *
243  *  rct2: 0x0068F41A
244  *  Called every 128 ticks
245  */
peep_128_tick_update(Peep * peep,int32_t index)246 static void peep_128_tick_update(Peep* peep, int32_t index)
247 {
248     auto* guest = peep->As<Guest>();
249     if (guest != nullptr)
250     {
251         guest->Tick128UpdateGuest(index);
252     }
253     else
254     {
255         auto* staff = peep->As<Staff>();
256         if (staff != nullptr)
257         {
258             staff->Tick128UpdateStaff();
259         }
260     }
261 }
262 
263 /*
264  * rct2: 0x68F3AE
265  * Set peep state to falling if path below has gone missing, return true if current path is valid, false if peep starts falling.
266  */
CheckForPath()267 bool Peep::CheckForPath()
268 {
269     PathCheckOptimisation++;
270     if ((PathCheckOptimisation & 0xF) != (sprite_index & 0xF))
271     {
272         // This condition makes the check happen less often
273         // As a side effect peeps hover for a short,
274         // random time when a path below them has been deleted
275         return true;
276     }
277 
278     TileElement* tile_element = map_get_first_element_at(NextLoc);
279 
280     uint8_t map_type = TILE_ELEMENT_TYPE_PATH;
281     if (GetNextIsSurface())
282     {
283         map_type = TILE_ELEMENT_TYPE_SURFACE;
284     }
285 
286     do
287     {
288         if (tile_element == nullptr)
289             break;
290         if (tile_element->GetType() == map_type)
291         {
292             if (NextLoc.z == tile_element->GetBaseZ())
293             {
294                 // Found a suitable path or surface
295                 return true;
296             }
297         }
298     } while (!(tile_element++)->IsLastForTile());
299 
300     // Found no suitable path
301     SetState(PeepState::Falling);
302     return false;
303 }
304 
GetActionSpriteType()305 PeepActionSpriteType Peep::GetActionSpriteType()
306 {
307     if (IsActionInterruptable())
308     { // PeepActionType::None1 or PeepActionType::None2
309         return PeepSpecialSpriteToSpriteTypeMap[SpecialSprite];
310     }
311 
312     if (EnumValue(Action) < std::size(PeepActionToSpriteTypeMap))
313     {
314         return PeepActionToSpriteTypeMap[EnumValue(Action)];
315     }
316 
317     openrct2_assert(
318         EnumValue(Action) >= std::size(PeepActionToSpriteTypeMap) && Action < PeepActionType::Idle, "Invalid peep action %u",
319         EnumValue(Action));
320     return PeepActionSpriteType::None;
321 }
322 
323 /*
324  *  rct2: 0x00693B58
325  */
UpdateCurrentActionSpriteType()326 void Peep::UpdateCurrentActionSpriteType()
327 {
328     if (EnumValue(SpriteType) >= std::size(g_peep_animation_entries))
329     {
330         return;
331     }
332     PeepActionSpriteType newActionSpriteType = GetActionSpriteType();
333     if (ActionSpriteType == newActionSpriteType)
334     {
335         return;
336     }
337 
338     Invalidate();
339     ActionSpriteType = newActionSpriteType;
340 
341     const rct_sprite_bounds* spriteBounds = &GetSpriteBounds(SpriteType, ActionSpriteType);
342     sprite_width = spriteBounds->sprite_width;
343     sprite_height_negative = spriteBounds->sprite_height_negative;
344     sprite_height_positive = spriteBounds->sprite_height_positive;
345 
346     Invalidate();
347 }
348 
349 /* rct2: 0x00693BE5 */
SwitchToSpecialSprite(uint8_t special_sprite_id)350 void Peep::SwitchToSpecialSprite(uint8_t special_sprite_id)
351 {
352     if (special_sprite_id == SpecialSprite)
353         return;
354 
355     SpecialSprite = special_sprite_id;
356 
357     if (IsActionInterruptable())
358     {
359         ActionSpriteImageOffset = 0;
360     }
361     UpdateCurrentActionSpriteType();
362 }
363 
StateReset()364 void Peep::StateReset()
365 {
366     SetState(PeepState::One);
367     SwitchToSpecialSprite(0);
368 }
369 
370 /** rct2: 0x00981D7C, 0x00981D7E */
371 static constexpr const CoordsXY word_981D7C[4] = {
372     { -2, 0 },
373     { 0, 2 },
374     { 2, 0 },
375     { 0, -2 },
376 };
377 
UpdateAction()378 std::optional<CoordsXY> Peep::UpdateAction()
379 {
380     int16_t xy_distance;
381     return UpdateAction(xy_distance);
382 }
383 
384 /**
385  *
386  *  rct2: 0x6939EB
387  * Also used to move peeps to the correct position to
388  * start an action. Returns true if the correct destination
389  * has not yet been reached. xy_distance is how close the
390  * peep is to the target.
391  */
UpdateAction(int16_t & xy_distance)392 std::optional<CoordsXY> Peep::UpdateAction(int16_t& xy_distance)
393 {
394     _unk_F1AEF0 = ActionSpriteImageOffset;
395     if (Action == PeepActionType::Idle)
396     {
397         Action = PeepActionType::Walking;
398     }
399 
400     CoordsXY differenceLoc = GetLocation();
401     differenceLoc -= GetDestination();
402 
403     int32_t x_delta = abs(differenceLoc.x);
404     int32_t y_delta = abs(differenceLoc.y);
405 
406     xy_distance = x_delta + y_delta;
407 
408     if (IsActionWalking())
409     {
410         if (xy_distance <= DestinationTolerance)
411         {
412             return std::nullopt;
413         }
414         int32_t nextDirection = 0;
415         if (x_delta < y_delta)
416         {
417             nextDirection = 8;
418             if (differenceLoc.y >= 0)
419             {
420                 nextDirection = 24;
421             }
422         }
423         else
424         {
425             nextDirection = 16;
426             if (differenceLoc.x >= 0)
427             {
428                 nextDirection = 0;
429             }
430         }
431         sprite_direction = nextDirection;
432         CoordsXY loc = { x, y };
433         loc += word_981D7C[nextDirection / 8];
434         WalkingFrameNum++;
435         const rct_peep_animation* peepAnimation = &GetPeepAnimation(SpriteType);
436         const uint8_t* imageOffset = peepAnimation[EnumValue(ActionSpriteType)].frame_offsets;
437         if (WalkingFrameNum >= peepAnimation[EnumValue(ActionSpriteType)].num_frames)
438         {
439             WalkingFrameNum = 0;
440         }
441         ActionSpriteImageOffset = imageOffset[WalkingFrameNum];
442         return loc;
443     }
444 
445     const rct_peep_animation* peepAnimation = &GetPeepAnimation(SpriteType);
446     ActionFrame++;
447 
448     // If last frame of action
449     if (ActionFrame >= peepAnimation[EnumValue(ActionSpriteType)].num_frames)
450     {
451         ActionSpriteImageOffset = 0;
452         Action = PeepActionType::Walking;
453         UpdateCurrentActionSpriteType();
454         return { { x, y } };
455     }
456     ActionSpriteImageOffset = peepAnimation[EnumValue(ActionSpriteType)].frame_offsets[ActionFrame];
457 
458     auto* guest = As<Guest>();
459     // If not throwing up and not at the frame where sick appears.
460     if (Action != PeepActionType::ThrowUp || ActionFrame != 15 || guest == nullptr)
461     {
462         return { { x, y } };
463     }
464 
465     // We are throwing up
466     guest->Hunger /= 2;
467     guest->NauseaTarget /= 2;
468 
469     if (guest->Nausea < 30)
470         guest->Nausea = 0;
471     else
472         guest->Nausea -= 30;
473 
474     WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_2;
475 
476     const auto curLoc = GetLocation();
477     Litter::Create({ curLoc, sprite_direction }, (sprite_index & 1) ? Litter::Type::VomitAlt : Litter::Type::Vomit);
478 
479     static constexpr OpenRCT2::Audio::SoundId coughs[4] = {
480         OpenRCT2::Audio::SoundId::Cough1,
481         OpenRCT2::Audio::SoundId::Cough2,
482         OpenRCT2::Audio::SoundId::Cough3,
483         OpenRCT2::Audio::SoundId::Cough4,
484     };
485     auto soundId = coughs[scenario_rand() & 3];
486     OpenRCT2::Audio::Play3D(soundId, curLoc);
487 
488     return { { x, y } };
489 }
490 
491 /**
492  *  rct2: 0x0069A409
493  * Decreases rider count if on/entering a ride.
494  */
peep_decrement_num_riders(Peep * peep)495 void peep_decrement_num_riders(Peep* peep)
496 {
497     if (peep->State == PeepState::OnRide || peep->State == PeepState::EnteringRide)
498     {
499         auto ride = get_ride(peep->CurrentRide);
500         if (ride != nullptr)
501         {
502             ride->num_riders = std::max(0, ride->num_riders - 1);
503             ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST;
504         }
505     }
506 }
507 
508 /**
509  * Call after changing a peeps state to insure that all relevant windows update.
510  * Note also increase ride count if on/entering a ride.
511  *  rct2: 0x0069A42F
512  */
peep_window_state_update(Peep * peep)513 void peep_window_state_update(Peep* peep)
514 {
515     rct_window* w = window_find_by_number(WC_PEEP, peep->sprite_index);
516     if (w != nullptr)
517         window_event_invalidate_call(w);
518 
519     if (peep->Is<Guest>())
520     {
521         if (peep->State == PeepState::OnRide || peep->State == PeepState::EnteringRide)
522         {
523             auto ride = get_ride(peep->CurrentRide);
524             if (ride != nullptr)
525             {
526                 ride->num_riders++;
527                 ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST;
528             }
529         }
530 
531         window_invalidate_by_number(WC_PEEP, peep->sprite_index);
532         window_invalidate_by_class(WC_GUEST_LIST);
533     }
534     else
535     {
536         window_invalidate_by_number(WC_PEEP, peep->sprite_index);
537         window_invalidate_by_class(WC_STAFF_LIST);
538     }
539 }
540 
Pickup()541 void Peep::Pickup()
542 {
543     auto* guest = As<Guest>();
544     if (guest != nullptr)
545     {
546         guest->RemoveFromRide();
547     }
548     MoveTo({ LOCATION_NULL, y, z });
549     SetState(PeepState::Picked);
550     SubState = 0;
551 }
552 
PickupAbort(int32_t old_x)553 void Peep::PickupAbort(int32_t old_x)
554 {
555     if (State != PeepState::Picked)
556         return;
557 
558     MoveTo({ old_x, y, z + 8 });
559 
560     if (x != LOCATION_NULL)
561     {
562         SetState(PeepState::Falling);
563         Action = PeepActionType::Walking;
564         SpecialSprite = 0;
565         ActionSpriteImageOffset = 0;
566         ActionSpriteType = PeepActionSpriteType::None;
567         PathCheckOptimisation = 0;
568     }
569 
570     gPickupPeepImage = UINT32_MAX;
571 }
572 
573 // Returns GameActions::Status::OK when a peep can be dropped at the given location. When apply is set to true the peep gets
574 // dropped.
Place(const TileCoordsXYZ & location,bool apply)575 std::unique_ptr<GameActions::Result> Peep::Place(const TileCoordsXYZ& location, bool apply)
576 {
577     auto* pathElement = map_get_path_element_at(location);
578     TileElement* tileElement = reinterpret_cast<TileElement*>(pathElement);
579     if (pathElement == nullptr)
580     {
581         tileElement = reinterpret_cast<TileElement*>(map_get_surface_element_at(location.ToCoordsXYZ()));
582     }
583     if (tileElement == nullptr)
584     {
585         return std::make_unique<GameActions::Result>(
586             GameActions::Status::InvalidParameters, STR_ERR_CANT_PLACE_PERSON_HERE, STR_NONE);
587     }
588 
589     // Set the coordinate of destination to be exactly
590     // in the middle of a tile.
591     CoordsXYZ destination = { location.ToCoordsXY().ToTileCentre(), tileElement->GetBaseZ() + 16 };
592 
593     if (!map_is_location_owned(destination))
594     {
595         return std::make_unique<GameActions::Result>(
596             GameActions::Status::NotOwned, STR_ERR_CANT_PLACE_PERSON_HERE, STR_LAND_NOT_OWNED_BY_PARK);
597     }
598 
599     if (auto res = MapCanConstructAt({ destination, destination.z, destination.z + (1 * 8) }, { 0b1111, 0 });
600         res->Error != GameActions::Status::Ok)
601     {
602         const auto stringId = std::get<rct_string_id>(res->ErrorMessage);
603         if (stringId != STR_RAISE_OR_LOWER_LAND_FIRST && stringId != STR_FOOTPATH_IN_THE_WAY)
604         {
605             return std::make_unique<GameActions::Result>(
606                 GameActions::Status::NoClearance, STR_ERR_CANT_PLACE_PERSON_HERE, stringId, res->ErrorMessageArgs.data());
607         }
608     }
609 
610     if (apply)
611     {
612         MoveTo(destination);
613         SetState(PeepState::Falling);
614         Action = PeepActionType::Walking;
615         SpecialSprite = 0;
616         ActionSpriteImageOffset = 0;
617         ActionSpriteType = PeepActionSpriteType::None;
618         PathCheckOptimisation = 0;
619         EntityTweener::Get().Reset();
620         auto* guest = As<Guest>();
621         if (guest != nullptr)
622         {
623             ActionSpriteType = PeepActionSpriteType::Invalid;
624             guest->HappinessTarget = std::max(guest->HappinessTarget - 10, 0);
625             UpdateCurrentActionSpriteType();
626         }
627     }
628 
629     return std::make_unique<GameActions::Result>();
630 }
631 
632 /**
633  *
634  *  rct2: 0x0069A535
635  */
peep_sprite_remove(Peep * peep)636 void peep_sprite_remove(Peep* peep)
637 {
638     auto* guest = peep->As<Guest>();
639     if (guest != nullptr)
640     {
641         guest->RemoveFromRide();
642     }
643     peep->Invalidate();
644 
645     window_close_by_number(WC_PEEP, peep->sprite_index);
646 
647     window_close_by_number(WC_FIRE_PROMPT, EnumValue(peep->Type));
648 
649     auto* staff = peep->As<Staff>();
650     // Needed for invalidations after sprite removal
651     bool wasGuest = staff == nullptr;
652     if (wasGuest)
653     {
654         News::DisableNewsItems(News::ItemType::PeepOnRide, peep->sprite_index);
655     }
656     else
657     {
658         staff->ClearPatrolArea();
659         staff_update_greyed_patrol_areas();
660 
661         News::DisableNewsItems(News::ItemType::Peep, staff->sprite_index);
662     }
663     sprite_remove(peep);
664 
665     auto intent = Intent(wasGuest ? INTENT_ACTION_REFRESH_GUEST_LIST : INTENT_ACTION_REFRESH_STAFF_LIST);
666     context_broadcast_intent(&intent);
667 }
668 
669 /**
670  * New function removes peep from park existence. Works with staff.
671  */
Remove()672 void Peep::Remove()
673 {
674     auto* guest = As<Guest>();
675     if (guest != nullptr)
676     {
677         if (!guest->OutsideOfPark)
678         {
679             decrement_guests_in_park();
680             auto intent = Intent(INTENT_ACTION_UPDATE_GUEST_COUNT);
681             context_broadcast_intent(&intent);
682         }
683         if (State == PeepState::EnteringPark)
684         {
685             decrement_guests_heading_for_park();
686         }
687     }
688     peep_sprite_remove(this);
689 }
690 
691 /**
692  * Falling and its subset drowning
693  *  rct2: 0x690028
694  */
UpdateFalling()695 void Peep::UpdateFalling()
696 {
697     if (Action == PeepActionType::Drowning)
698     {
699         // Check to see if we are ready to drown.
700         UpdateAction();
701         Invalidate();
702         if (Action == PeepActionType::Drowning)
703             return;
704 
705         if (gConfigNotifications.guest_died)
706         {
707             auto ft = Formatter();
708             FormatNameTo(ft);
709             News::AddItemToQueue(News::ItemType::Blank, STR_NEWS_ITEM_GUEST_DROWNED, x | (y << 16), ft);
710         }
711 
712         gParkRatingCasualtyPenalty = std::min(gParkRatingCasualtyPenalty + 25, 1000);
713         Remove();
714         return;
715     }
716 
717     // If not drowning then falling. Note: peeps 'fall' after leaving a ride/enter the park.
718     TileElement* tile_element = map_get_first_element_at(CoordsXY{ x, y });
719     TileElement* saved_map = nullptr;
720     int32_t saved_height = 0;
721 
722     if (tile_element != nullptr)
723     {
724         do
725         {
726             // If a path check if we are on it
727             if (tile_element->GetType() == TILE_ELEMENT_TYPE_PATH)
728             {
729                 int32_t height = map_height_from_slope(
730                                      { x, y }, tile_element->AsPath()->GetSlopeDirection(), tile_element->AsPath()->IsSloped())
731                     + tile_element->GetBaseZ();
732 
733                 if (height < z - 1 || height > z + 4)
734                     continue;
735 
736                 saved_height = height;
737                 saved_map = tile_element;
738                 break;
739             } // If a surface get the height and see if we are on it
740             else if (tile_element->GetType() == TILE_ELEMENT_TYPE_SURFACE)
741             {
742                 // If the surface is water check to see if we could be drowning
743                 if (tile_element->AsSurface()->GetWaterHeight() > 0)
744                 {
745                     int32_t height = tile_element->AsSurface()->GetWaterHeight();
746 
747                     if (height - 4 >= z && height < z + 20)
748                     {
749                         // Looks like we are drowning!
750                         MoveTo({ x, y, height });
751 
752                         auto* guest = As<Guest>();
753                         if (guest != nullptr)
754                         {
755                             // Drop balloon if held
756                             peep_release_balloon(guest, height);
757                             guest->InsertNewThought(PeepThoughtType::Drowning);
758                         }
759 
760                         Action = PeepActionType::Drowning;
761                         ActionFrame = 0;
762                         ActionSpriteImageOffset = 0;
763 
764                         UpdateCurrentActionSpriteType();
765                         peep_window_state_update(this);
766                         return;
767                     }
768                 }
769                 int32_t map_height = tile_element_height({ x, y });
770                 if (map_height < z || map_height - 4 > z)
771                     continue;
772                 saved_height = map_height;
773                 saved_map = tile_element;
774             } // If not a path or surface go see next element
775             else
776                 continue;
777         } while (!(tile_element++)->IsLastForTile());
778     }
779 
780     // This will be null if peep is falling
781     if (saved_map == nullptr)
782     {
783         if (z <= 1)
784         {
785             // Remove peep if it has gone to the void
786             Remove();
787             return;
788         }
789         MoveTo({ x, y, z - 2 });
790         return;
791     }
792 
793     MoveTo({ x, y, saved_height });
794 
795     NextLoc = { CoordsXY{ x, y }.ToTileStart(), saved_map->GetBaseZ() };
796 
797     if (saved_map->GetType() != TILE_ELEMENT_TYPE_PATH)
798     {
799         SetNextFlags(0, false, true);
800     }
801     else
802     {
803         SetNextFlags(saved_map->AsPath()->GetSlopeDirection(), saved_map->AsPath()->IsSloped(), false);
804     }
805     SetState(PeepState::One);
806 }
807 
808 /**
809  *
810  *  rct2: 0x6902A2
811  */
Update1()812 void Peep::Update1()
813 {
814     if (!CheckForPath())
815         return;
816 
817     if (Is<Guest>())
818     {
819         SetState(PeepState::Walking);
820     }
821     else
822     {
823         SetState(PeepState::Patrolling);
824     }
825 
826     SetDestination(GetLocation(), 10);
827     PeepDirection = sprite_direction >> 3;
828 }
829 
SetState(PeepState new_state)830 void Peep::SetState(PeepState new_state)
831 {
832     peep_decrement_num_riders(this);
833     State = new_state;
834     peep_window_state_update(this);
835 }
836 
837 /**
838  *
839  *  rct2: 0x690009
840  */
UpdatePicked()841 void Peep::UpdatePicked()
842 {
843     if (gCurrentTicks & 0x1F)
844         return;
845     SubState++;
846     auto* guest = As<Guest>();
847     if (SubState == 13 && guest != nullptr)
848     {
849         guest->InsertNewThought(PeepThoughtType::Help);
850     }
851 }
852 
853 /* From peep_update */
peep_update_thoughts(Guest * peep)854 static void peep_update_thoughts(Guest* peep)
855 {
856     // Thoughts must always have a gap of at least
857     // 220 ticks in age between them. In order to
858     // allow this when a thought is new it enters
859     // a holding zone. Before it becomes fresh.
860     int32_t add_fresh = 1;
861     int32_t fresh_thought = -1;
862     for (int32_t i = 0; i < PEEP_MAX_THOUGHTS; i++)
863     {
864         if (peep->Thoughts[i].type == PeepThoughtType::None)
865             break;
866 
867         if (peep->Thoughts[i].freshness == 1)
868         {
869             add_fresh = 0;
870             // If thought is fresh we wait 220 ticks
871             // before allowing a new thought to become fresh.
872             if (++peep->Thoughts[i].fresh_timeout >= 220)
873             {
874                 peep->Thoughts[i].fresh_timeout = 0;
875                 // Thought is no longer fresh
876                 peep->Thoughts[i].freshness++;
877                 add_fresh = 1;
878             }
879         }
880         else if (peep->Thoughts[i].freshness > 1)
881         {
882             if (++peep->Thoughts[i].fresh_timeout == 0)
883             {
884                 // When thought is older than ~6900 ticks remove it
885                 if (++peep->Thoughts[i].freshness >= 28)
886                 {
887                     peep->WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_THOUGHTS;
888 
889                     // Clear top thought, push others up
890                     if (i < PEEP_MAX_THOUGHTS - 2)
891                     {
892                         memmove(&peep->Thoughts[i], &peep->Thoughts[i + 1], sizeof(PeepThought) * (PEEP_MAX_THOUGHTS - i - 1));
893                     }
894                     peep->Thoughts[PEEP_MAX_THOUGHTS - 1].type = PeepThoughtType::None;
895                 }
896             }
897         }
898         else
899         {
900             fresh_thought = i;
901         }
902     }
903     // If there are no fresh thoughts
904     // a previously new thought can become
905     // fresh.
906     if (add_fresh && fresh_thought != -1)
907     {
908         peep->Thoughts[fresh_thought].freshness = 1;
909         peep->WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_THOUGHTS;
910     }
911 }
912 
913 /**
914  *
915  *  rct2: 0x0068FC1E
916  */
Update()917 void Peep::Update()
918 {
919     auto* guest = As<Guest>();
920     if (guest != nullptr)
921     {
922         if (guest->PreviousRide != RIDE_ID_NULL)
923             if (++guest->PreviousRideTimeOut >= 720)
924                 guest->PreviousRide = RIDE_ID_NULL;
925 
926         peep_update_thoughts(guest);
927     }
928 
929     // Walking speed logic
930     uint32_t stepsToTake = Energy;
931     if (stepsToTake < 95 && State == PeepState::Queuing)
932         stepsToTake = 95;
933     if ((PeepFlags & PEEP_FLAGS_SLOW_WALK) && State != PeepState::Queuing)
934         stepsToTake /= 2;
935     if (IsActionWalking() && GetNextIsSloped())
936     {
937         stepsToTake /= 2;
938         if (State == PeepState::Queuing)
939             stepsToTake += stepsToTake / 2;
940     }
941 
942     uint32_t carryCheck = StepProgress + stepsToTake;
943     StepProgress = carryCheck;
944     if (carryCheck <= 255)
945     {
946         if (guest != nullptr)
947         {
948             guest->UpdateEasterEggInteractions();
949         }
950     }
951     else
952     {
953         // loc_68FD2F
954         switch (State)
955         {
956             case PeepState::Falling:
957                 UpdateFalling();
958                 break;
959             case PeepState::One:
960                 Update1();
961                 break;
962             case PeepState::OnRide:
963                 // No action
964                 break;
965             case PeepState::Picked:
966                 UpdatePicked();
967                 break;
968             default:
969             {
970                 if (guest != nullptr)
971                 {
972                     guest->UpdateGuest();
973                 }
974                 else
975                 {
976                     auto* staff = As<Staff>();
977                     if (staff != nullptr)
978                     {
979                         staff->UpdateStaff(stepsToTake);
980                     }
981                     else
982                     {
983                         assert(false);
984                     }
985                 }
986                 break;
987             }
988         }
989     }
990 }
991 
992 /**
993  *
994  *  rct2: 0x0069BF41
995  */
peep_problem_warnings_update()996 void peep_problem_warnings_update()
997 {
998     Ride* ride;
999     uint32_t hunger_counter = 0, lost_counter = 0, noexit_counter = 0, thirst_counter = 0, litter_counter = 0,
1000              disgust_counter = 0, toilet_counter = 0, vandalism_counter = 0;
1001     uint8_t* warning_throttle = gPeepWarningThrottle;
1002 
1003     for (auto peep : EntityList<Guest>())
1004     {
1005         if (peep->OutsideOfPark || peep->Thoughts[0].freshness > 5)
1006             continue;
1007 
1008         switch (peep->Thoughts[0].type)
1009         {
1010             case PeepThoughtType::Lost: // 0x10
1011                 lost_counter++;
1012                 break;
1013 
1014             case PeepThoughtType::Hungry: // 0x14
1015                 if (peep->GuestHeadingToRideId == RIDE_ID_NULL)
1016                 {
1017                     hunger_counter++;
1018                     break;
1019                 }
1020                 ride = get_ride(peep->GuestHeadingToRideId);
1021                 if (ride != nullptr && !ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE))
1022                     hunger_counter++;
1023                 break;
1024 
1025             case PeepThoughtType::Thirsty:
1026                 if (peep->GuestHeadingToRideId == RIDE_ID_NULL)
1027                 {
1028                     thirst_counter++;
1029                     break;
1030                 }
1031                 ride = get_ride(peep->GuestHeadingToRideId);
1032                 if (ride != nullptr && !ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_SELLS_DRINKS))
1033                     thirst_counter++;
1034                 break;
1035 
1036             case PeepThoughtType::Toilet:
1037                 if (peep->GuestHeadingToRideId == RIDE_ID_NULL)
1038                 {
1039                     toilet_counter++;
1040                     break;
1041                 }
1042                 ride = get_ride(peep->GuestHeadingToRideId);
1043                 if (ride != nullptr && !ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_TOILET))
1044                     toilet_counter++;
1045                 break;
1046 
1047             case PeepThoughtType::BadLitter: // 0x1a
1048                 litter_counter++;
1049                 break;
1050             case PeepThoughtType::CantFindExit: // 0x1b
1051                 noexit_counter++;
1052                 break;
1053             case PeepThoughtType::PathDisgusting: // 0x1f
1054                 disgust_counter++;
1055                 break;
1056             case PeepThoughtType::Vandalism: // 0x21
1057                 vandalism_counter++;
1058                 break;
1059             default:
1060                 break;
1061         }
1062     }
1063     // could maybe be packed into a loop, would lose a lot of clarity though
1064     if (warning_throttle[0])
1065         --warning_throttle[0];
1066     else if (hunger_counter >= PEEP_HUNGER_WARNING_THRESHOLD && hunger_counter >= gNumGuestsInPark / 16)
1067     {
1068         warning_throttle[0] = 4;
1069         if (gConfigNotifications.guest_warnings)
1070         {
1071             News::AddItemToQueue(News::ItemType::Peeps, STR_PEEPS_ARE_HUNGRY, 20, {});
1072         }
1073     }
1074 
1075     if (warning_throttle[1])
1076         --warning_throttle[1];
1077     else if (thirst_counter >= PEEP_THIRST_WARNING_THRESHOLD && thirst_counter >= gNumGuestsInPark / 16)
1078     {
1079         warning_throttle[1] = 4;
1080         if (gConfigNotifications.guest_warnings)
1081         {
1082             News::AddItemToQueue(News::ItemType::Peeps, STR_PEEPS_ARE_THIRSTY, 21, {});
1083         }
1084     }
1085 
1086     if (warning_throttle[2])
1087         --warning_throttle[2];
1088     else if (toilet_counter >= PEEP_TOILET_WARNING_THRESHOLD && toilet_counter >= gNumGuestsInPark / 16)
1089     {
1090         warning_throttle[2] = 4;
1091         if (gConfigNotifications.guest_warnings)
1092         {
1093             News::AddItemToQueue(News::ItemType::Peeps, STR_PEEPS_CANT_FIND_TOILET, 22, {});
1094         }
1095     }
1096 
1097     if (warning_throttle[3])
1098         --warning_throttle[3];
1099     else if (litter_counter >= PEEP_LITTER_WARNING_THRESHOLD && litter_counter >= gNumGuestsInPark / 32)
1100     {
1101         warning_throttle[3] = 4;
1102         if (gConfigNotifications.guest_warnings)
1103         {
1104             News::AddItemToQueue(News::ItemType::Peeps, STR_PEEPS_DISLIKE_LITTER, 26, {});
1105         }
1106     }
1107 
1108     if (warning_throttle[4])
1109         --warning_throttle[4];
1110     else if (disgust_counter >= PEEP_DISGUST_WARNING_THRESHOLD && disgust_counter >= gNumGuestsInPark / 32)
1111     {
1112         warning_throttle[4] = 4;
1113         if (gConfigNotifications.guest_warnings)
1114         {
1115             News::AddItemToQueue(News::ItemType::Peeps, STR_PEEPS_DISGUSTED_BY_PATHS, 31, {});
1116         }
1117     }
1118 
1119     if (warning_throttle[5])
1120         --warning_throttle[5];
1121     else if (vandalism_counter >= PEEP_VANDALISM_WARNING_THRESHOLD && vandalism_counter >= gNumGuestsInPark / 32)
1122     {
1123         warning_throttle[5] = 4;
1124         if (gConfigNotifications.guest_warnings)
1125         {
1126             News::AddItemToQueue(News::ItemType::Peeps, STR_PEEPS_DISLIKE_VANDALISM, 33, {});
1127         }
1128     }
1129 
1130     if (warning_throttle[6])
1131         --warning_throttle[6];
1132     else if (noexit_counter >= PEEP_NOEXIT_WARNING_THRESHOLD)
1133     {
1134         warning_throttle[6] = 4;
1135         if (gConfigNotifications.guest_warnings)
1136         {
1137             News::AddItemToQueue(News::ItemType::Peeps, STR_PEEPS_GETTING_LOST_OR_STUCK, 27, {});
1138         }
1139     }
1140     else if (lost_counter >= PEEP_LOST_WARNING_THRESHOLD)
1141     {
1142         warning_throttle[6] = 4;
1143         if (gConfigNotifications.guest_warnings)
1144         {
1145             News::AddItemToQueue(News::ItemType::Peeps, STR_PEEPS_GETTING_LOST_OR_STUCK, 16, {});
1146         }
1147     }
1148 }
1149 
peep_stop_crowd_noise()1150 void peep_stop_crowd_noise()
1151 {
1152     if (_crowdSoundChannel != nullptr)
1153     {
1154         Mixer_Stop_Channel(_crowdSoundChannel);
1155         _crowdSoundChannel = nullptr;
1156     }
1157 }
1158 
1159 /**
1160  *
1161  *  rct2: 0x006BD18A
1162  */
peep_update_crowd_noise()1163 void peep_update_crowd_noise()
1164 {
1165     if (OpenRCT2::Audio::gGameSoundsOff)
1166         return;
1167 
1168     if (!gConfigSound.sound_enabled)
1169         return;
1170 
1171     if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR)
1172         return;
1173 
1174     auto viewport = g_music_tracking_viewport;
1175     if (viewport == nullptr)
1176         return;
1177 
1178     // Count the number of peeps visible
1179     auto visiblePeeps = 0;
1180 
1181     for (auto peep : EntityList<Guest>())
1182     {
1183         if (peep->x == LOCATION_NULL)
1184             continue;
1185         if (viewport->viewPos.x > peep->SpriteRect.GetRight())
1186             continue;
1187         if (viewport->viewPos.x + viewport->view_width < peep->SpriteRect.GetLeft())
1188             continue;
1189         if (viewport->viewPos.y > peep->SpriteRect.GetBottom())
1190             continue;
1191         if (viewport->viewPos.y + viewport->view_height < peep->SpriteRect.GetTop())
1192             continue;
1193 
1194         visiblePeeps += peep->State == PeepState::Queuing ? 1 : 2;
1195     }
1196 
1197     // This function doesn't account for the fact that the screen might be so big that 100 peeps could potentially be very
1198     // spread out and therefore not produce any crowd noise. Perhaps a more sophisticated solution would check how many peeps
1199     // were in close proximity to each other.
1200 
1201     // Allows queuing peeps to make half as much noise, and at least 6 peeps must be visible for any crowd noise
1202     visiblePeeps = (visiblePeeps / 2) - 6;
1203     if (visiblePeeps < 0)
1204     {
1205         // Mute crowd noise
1206         if (_crowdSoundChannel != nullptr)
1207         {
1208             Mixer_Stop_Channel(_crowdSoundChannel);
1209             _crowdSoundChannel = nullptr;
1210         }
1211     }
1212     else
1213     {
1214         int32_t volume;
1215 
1216         // Formula to scale peeps to dB where peeps [0, 120] scales approximately logarithmically to [-3314, -150] dB/100
1217         // 207360000 maybe related to DSBVOLUME_MIN which is -10,000 (dB/100)
1218         volume = 120 - std::min(visiblePeeps, 120);
1219         volume = volume * volume * volume * volume;
1220         volume = (((207360000 - volume) / viewport->zoom) - 207360000) / 65536 - 150;
1221 
1222         // Load and play crowd noise if needed and set volume
1223         if (_crowdSoundChannel == nullptr)
1224         {
1225             _crowdSoundChannel = Mixer_Play_Music(PATH_ID_CSS2, MIXER_LOOP_INFINITE, false);
1226             if (_crowdSoundChannel != nullptr)
1227             {
1228                 Mixer_Channel_SetGroup(_crowdSoundChannel, OpenRCT2::Audio::MixerGroup::Sound);
1229             }
1230         }
1231         if (_crowdSoundChannel != nullptr)
1232         {
1233             Mixer_Channel_Volume(_crowdSoundChannel, DStoMixerVolume(volume));
1234         }
1235     }
1236 }
1237 
1238 /**
1239  *
1240  *  rct2: 0x0069BE9B
1241  */
peep_applause()1242 void peep_applause()
1243 {
1244     for (auto peep : EntityList<Guest>())
1245     {
1246         if (peep->OutsideOfPark)
1247             continue;
1248 
1249         // Release balloon
1250         peep_release_balloon(peep, peep->z + 9);
1251 
1252         // Clap
1253         if ((peep->State == PeepState::Walking || peep->State == PeepState::Queuing) && peep->IsActionInterruptable())
1254         {
1255             peep->Action = PeepActionType::Clap;
1256             peep->ActionFrame = 0;
1257             peep->ActionSpriteImageOffset = 0;
1258             peep->UpdateCurrentActionSpriteType();
1259         }
1260     }
1261 
1262     // Play applause noise
1263     OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Applause, 0, context_get_width() / 2);
1264 }
1265 
1266 /**
1267  *
1268  *  rct2: 0x0069C35E
1269  */
peep_update_days_in_queue()1270 void peep_update_days_in_queue()
1271 {
1272     for (auto peep : EntityList<Guest>())
1273     {
1274         if (!peep->OutsideOfPark && peep->State == PeepState::Queuing)
1275         {
1276             if (peep->DaysInQueue < 255)
1277             {
1278                 peep->DaysInQueue += 1;
1279             }
1280         }
1281     }
1282 }
1283 
FormatActionTo(Formatter & ft) const1284 void Peep::FormatActionTo(Formatter& ft) const
1285 {
1286     switch (State)
1287     {
1288         case PeepState::Falling:
1289             ft.Add<rct_string_id>(Action == PeepActionType::Drowning ? STR_DROWNING : STR_WALKING);
1290             break;
1291         case PeepState::One:
1292             ft.Add<rct_string_id>(STR_WALKING);
1293             break;
1294         case PeepState::OnRide:
1295         case PeepState::LeavingRide:
1296         case PeepState::EnteringRide:
1297         {
1298             auto ride = get_ride(CurrentRide);
1299             if (ride != nullptr)
1300             {
1301                 ft.Add<rct_string_id>(
1302                     ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IN_RIDE) ? STR_IN_RIDE : STR_ON_RIDE);
1303                 ride->FormatNameTo(ft);
1304             }
1305             else
1306             {
1307                 ft.Add<rct_string_id>(STR_ON_RIDE).Add<rct_string_id>(STR_NONE);
1308             }
1309             break;
1310         }
1311         case PeepState::Buying:
1312         {
1313             ft.Add<rct_string_id>(STR_AT_RIDE);
1314             auto ride = get_ride(CurrentRide);
1315             if (ride != nullptr)
1316             {
1317                 ride->FormatNameTo(ft);
1318             }
1319             else
1320             {
1321                 ft.Add<rct_string_id>(STR_NONE);
1322             }
1323             break;
1324         }
1325         case PeepState::Walking:
1326         case PeepState::UsingBin:
1327         {
1328             auto* guest = As<Guest>();
1329             if (guest != nullptr)
1330             {
1331                 if (guest->GuestHeadingToRideId != RIDE_ID_NULL)
1332                 {
1333                     auto ride = get_ride(guest->GuestHeadingToRideId);
1334                     if (ride != nullptr)
1335                     {
1336                         ft.Add<rct_string_id>(STR_HEADING_FOR);
1337                         ride->FormatNameTo(ft);
1338                     }
1339                 }
1340                 else
1341                 {
1342                     ft.Add<rct_string_id>((PeepFlags & PEEP_FLAGS_LEAVING_PARK) ? STR_LEAVING_PARK : STR_WALKING);
1343                 }
1344             }
1345             break;
1346         }
1347         case PeepState::QueuingFront:
1348         case PeepState::Queuing:
1349         {
1350             auto ride = get_ride(CurrentRide);
1351             if (ride != nullptr)
1352             {
1353                 ft.Add<rct_string_id>(STR_QUEUING_FOR);
1354                 ride->FormatNameTo(ft);
1355             }
1356             break;
1357         }
1358         case PeepState::Sitting:
1359             ft.Add<rct_string_id>(STR_SITTING);
1360             break;
1361         case PeepState::Watching:
1362             if (CurrentRide != RIDE_ID_NULL)
1363             {
1364                 auto ride = get_ride(CurrentRide);
1365                 if (ride != nullptr)
1366                 {
1367                     ft.Add<rct_string_id>((StandingFlags & 0x1) ? STR_WATCHING_CONSTRUCTION_OF : STR_WATCHING_RIDE);
1368                     ride->FormatNameTo(ft);
1369                 }
1370             }
1371             else
1372             {
1373                 ft.Add<rct_string_id>((StandingFlags & 0x1) ? STR_WATCHING_NEW_RIDE_BEING_CONSTRUCTED : STR_LOOKING_AT_SCENERY);
1374             }
1375             break;
1376         case PeepState::Picked:
1377             ft.Add<rct_string_id>(STR_SELECT_LOCATION);
1378             break;
1379         case PeepState::Patrolling:
1380         case PeepState::EnteringPark:
1381         case PeepState::LeavingPark:
1382             ft.Add<rct_string_id>(STR_WALKING);
1383             break;
1384         case PeepState::Mowing:
1385             ft.Add<rct_string_id>(STR_MOWING_GRASS);
1386             break;
1387         case PeepState::Sweeping:
1388             ft.Add<rct_string_id>(STR_SWEEPING_FOOTPATH);
1389             break;
1390         case PeepState::Watering:
1391             ft.Add<rct_string_id>(STR_WATERING_GARDENS);
1392             break;
1393         case PeepState::EmptyingBin:
1394             ft.Add<rct_string_id>(STR_EMPTYING_LITTER_BIN);
1395             break;
1396         case PeepState::Answering:
1397             if (SubState == 0)
1398             {
1399                 ft.Add<rct_string_id>(STR_WALKING);
1400             }
1401             else if (SubState == 1)
1402             {
1403                 ft.Add<rct_string_id>(STR_ANSWERING_RADIO_CALL);
1404             }
1405             else
1406             {
1407                 ft.Add<rct_string_id>(STR_RESPONDING_TO_RIDE_BREAKDOWN_CALL);
1408                 auto ride = get_ride(CurrentRide);
1409                 if (ride != nullptr)
1410                 {
1411                     ride->FormatNameTo(ft);
1412                 }
1413                 else
1414                 {
1415                     ft.Add<rct_string_id>(STR_NONE);
1416                 }
1417             }
1418             break;
1419         case PeepState::Fixing:
1420         {
1421             ft.Add<rct_string_id>(STR_FIXING_RIDE);
1422             auto ride = get_ride(CurrentRide);
1423             if (ride != nullptr)
1424             {
1425                 ride->FormatNameTo(ft);
1426             }
1427             else
1428             {
1429                 ft.Add<rct_string_id>(STR_NONE);
1430             }
1431             break;
1432         }
1433         case PeepState::HeadingToInspection:
1434         {
1435             ft.Add<rct_string_id>(STR_HEADING_TO_RIDE_FOR_INSPECTION);
1436             auto ride = get_ride(CurrentRide);
1437             if (ride != nullptr)
1438             {
1439                 ride->FormatNameTo(ft);
1440             }
1441             else
1442             {
1443                 ft.Add<rct_string_id>(STR_NONE);
1444             }
1445             break;
1446         }
1447         case PeepState::Inspecting:
1448         {
1449             ft.Add<rct_string_id>(STR_INSPECTING_RIDE);
1450             auto ride = get_ride(CurrentRide);
1451             if (ride != nullptr)
1452             {
1453                 ride->FormatNameTo(ft);
1454             }
1455             else
1456             {
1457                 ft.Add<rct_string_id>(STR_NONE);
1458             }
1459             break;
1460         }
1461     }
1462 }
1463 
1464 static constexpr const rct_string_id _staffNames[] = {
1465     STR_HANDYMAN_X,
1466     STR_MECHANIC_X,
1467     STR_SECURITY_GUARD_X,
1468     STR_ENTERTAINER_X,
1469 };
1470 
FormatNameTo(Formatter & ft) const1471 void Peep::FormatNameTo(Formatter& ft) const
1472 {
1473     if (Name == nullptr)
1474     {
1475         auto* staff = As<Staff>();
1476         if (staff != nullptr)
1477         {
1478             auto staffNameIndex = static_cast<uint8_t>(staff->AssignedStaffType);
1479             if (staffNameIndex >= std::size(_staffNames))
1480             {
1481                 staffNameIndex = 0;
1482             }
1483 
1484             ft.Add<rct_string_id>(_staffNames[staffNameIndex]);
1485             ft.Add<uint32_t>(Id);
1486         }
1487         else if (gParkFlags & PARK_FLAGS_SHOW_REAL_GUEST_NAMES)
1488         {
1489             auto realNameStringId = get_real_name_string_id_from_id(Id);
1490             ft.Add<rct_string_id>(realNameStringId);
1491         }
1492         else
1493         {
1494             ft.Add<rct_string_id>(STR_GUEST_X).Add<uint32_t>(Id);
1495         }
1496     }
1497     else
1498     {
1499         ft.Add<rct_string_id>(STR_STRING).Add<const char*>(Name);
1500     }
1501 }
1502 
GetName() const1503 std::string Peep::GetName() const
1504 {
1505     Formatter ft;
1506     FormatNameTo(ft);
1507     return format_string(STR_STRINGID, ft.Data());
1508 }
1509 
SetName(std::string_view value)1510 bool Peep::SetName(std::string_view value)
1511 {
1512     if (value.empty())
1513     {
1514         std::free(Name);
1515         Name = nullptr;
1516         return true;
1517     }
1518 
1519     auto newNameMemory = static_cast<char*>(std::malloc(value.size() + 1));
1520     if (newNameMemory != nullptr)
1521     {
1522         std::memcpy(newNameMemory, value.data(), value.size());
1523         newNameMemory[value.size()] = '\0';
1524         std::free(Name);
1525         Name = newNameMemory;
1526         return true;
1527     }
1528     return false;
1529 }
1530 
IsActionWalking() const1531 bool Peep::IsActionWalking() const
1532 {
1533     return Action == PeepActionType::Walking;
1534 }
1535 
IsActionIdle() const1536 bool Peep::IsActionIdle() const
1537 {
1538     return Action == PeepActionType::Idle;
1539 }
1540 
IsActionInterruptable() const1541 bool Peep::IsActionInterruptable() const
1542 {
1543     return IsActionIdle() || IsActionWalking();
1544 }
1545 
peep_set_map_tooltip(Peep * peep)1546 void peep_set_map_tooltip(Peep* peep)
1547 {
1548     auto ft = Formatter();
1549     auto* guest = peep->As<Guest>();
1550     if (guest != nullptr)
1551     {
1552         ft.Add<rct_string_id>((peep->PeepFlags & PEEP_FLAGS_TRACKING) ? STR_TRACKED_GUEST_MAP_TIP : STR_GUEST_MAP_TIP);
1553         ft.Add<uint32_t>(get_peep_face_sprite_small(guest));
1554         guest->FormatNameTo(ft);
1555         guest->FormatActionTo(ft);
1556     }
1557     else
1558     {
1559         ft.Add<rct_string_id>(STR_STAFF_MAP_TIP);
1560         peep->FormatNameTo(ft);
1561         peep->FormatActionTo(ft);
1562     }
1563 
1564     auto intent = Intent(INTENT_ACTION_SET_MAP_TOOLTIP);
1565     intent.putExtra(INTENT_EXTRA_FORMATTER, &ft);
1566     context_broadcast_intent(&intent);
1567 }
1568 
1569 /**
1570  *  rct2: 0x00693BAB
1571  */
SwitchNextActionSpriteType()1572 void Peep::SwitchNextActionSpriteType()
1573 {
1574     // TBD: Add nextActionSpriteType as function parameter and make peep->NextActionSpriteType obsolete?
1575     if (NextActionSpriteType != ActionSpriteType)
1576     {
1577         Invalidate();
1578         ActionSpriteType = NextActionSpriteType;
1579         const rct_sprite_bounds* spriteBounds = &GetSpriteBounds(SpriteType, NextActionSpriteType);
1580         sprite_width = spriteBounds->sprite_width;
1581         sprite_height_negative = spriteBounds->sprite_height_negative;
1582         sprite_height_positive = spriteBounds->sprite_height_positive;
1583         Invalidate();
1584     }
1585 }
1586 
1587 /**
1588  *
1589  *  rct2: 0x00693EF2
1590  */
peep_return_to_centre_of_tile(Peep * peep)1591 static void peep_return_to_centre_of_tile(Peep* peep)
1592 {
1593     peep->PeepDirection = direction_reverse(peep->PeepDirection);
1594     auto destination = peep->GetLocation().ToTileCentre();
1595     peep->SetDestination(destination, 5);
1596 }
1597 
1598 /**
1599  *
1600  *  rct2: 0x00693f2C
1601  */
peep_interact_with_entrance(Peep * peep,const CoordsXYE & coords,uint8_t & pathing_result)1602 static bool peep_interact_with_entrance(Peep* peep, const CoordsXYE& coords, uint8_t& pathing_result)
1603 {
1604     auto tile_element = coords.element;
1605     uint8_t entranceType = tile_element->AsEntrance()->GetEntranceType();
1606     auto rideIndex = tile_element->AsEntrance()->GetRideIndex();
1607 
1608     if ((entranceType == ENTRANCE_TYPE_RIDE_ENTRANCE) || (entranceType == ENTRANCE_TYPE_RIDE_EXIT))
1609     {
1610         // If an entrance or exit that doesn't belong to the ride we are queuing for ignore the entrance/exit
1611         // This can happen when paths clip through entrance/exits
1612         if (peep->State == PeepState::Queuing && peep->CurrentRide != rideIndex)
1613         {
1614             return false;
1615         }
1616     }
1617     // Store some details to determine when to override the default
1618     // behaviour (defined below) for when staff attempt to enter a ride
1619     // to fix/inspect it.
1620     if (entranceType == ENTRANCE_TYPE_RIDE_EXIT)
1621     {
1622         pathing_result |= PATHING_RIDE_EXIT;
1623         _peepRideEntranceExitElement = tile_element;
1624     }
1625     else if (entranceType == ENTRANCE_TYPE_RIDE_ENTRANCE)
1626     {
1627         pathing_result |= PATHING_RIDE_ENTRANCE;
1628         _peepRideEntranceExitElement = tile_element;
1629     }
1630 
1631     if (entranceType == ENTRANCE_TYPE_RIDE_EXIT)
1632     {
1633         // Default guest/staff behaviour attempting to enter a
1634         // ride exit is to turn around.
1635         peep->InteractionRideIndex = RIDE_ID_NULL;
1636         peep_return_to_centre_of_tile(peep);
1637         return true;
1638     }
1639 
1640     if (entranceType == ENTRANCE_TYPE_RIDE_ENTRANCE)
1641     {
1642         auto ride = get_ride(rideIndex);
1643         if (ride == nullptr)
1644             return false;
1645 
1646         auto* guest = peep->As<Guest>();
1647         if (guest == nullptr)
1648         {
1649             // Default staff behaviour attempting to enter a
1650             // ride entrance is to turn around.
1651             peep->InteractionRideIndex = RIDE_ID_NULL;
1652             peep_return_to_centre_of_tile(peep);
1653             return true;
1654         }
1655 
1656         if (guest->State == PeepState::Queuing)
1657         {
1658             // Guest is in the ride queue.
1659             guest->RideSubState = PeepRideSubState::AtQueueFront;
1660             guest->ActionSpriteImageOffset = _unk_F1AEF0;
1661             return true;
1662         }
1663 
1664         // Guest is on a normal path, i.e. ride has no queue.
1665         if (guest->InteractionRideIndex == rideIndex)
1666         {
1667             // Peep is retrying the ride entrance without leaving
1668             // the path tile and without trying any other ride
1669             // attached to this path tile. i.e. stick with the
1670             // peeps previous decision not to go on the ride.
1671             peep_return_to_centre_of_tile(guest);
1672             return true;
1673         }
1674 
1675         guest->TimeLost = 0;
1676         auto stationNum = tile_element->AsEntrance()->GetStationIndex();
1677         // Guest walks up to the ride for the first time since entering
1678         // the path tile or since considering another ride attached to
1679         // the path tile.
1680         if (!guest->ShouldGoOnRide(ride, stationNum, false, false))
1681         {
1682             // Peep remembers that this is the last ride they
1683             // considered while on this path tile.
1684             guest->InteractionRideIndex = rideIndex;
1685             peep_return_to_centre_of_tile(guest);
1686             return true;
1687         }
1688 
1689         // Guest has decided to go on the ride.
1690         guest->ActionSpriteImageOffset = _unk_F1AEF0;
1691         guest->InteractionRideIndex = rideIndex;
1692 
1693         uint16_t previous_last = ride->stations[stationNum].LastPeepInQueue;
1694         ride->stations[stationNum].LastPeepInQueue = guest->sprite_index;
1695         guest->GuestNextInQueue = previous_last;
1696         ride->stations[stationNum].QueueLength++;
1697 
1698         guest->CurrentRide = rideIndex;
1699         guest->CurrentRideStation = stationNum;
1700         guest->DaysInQueue = 0;
1701         guest->SetState(PeepState::Queuing);
1702         guest->RideSubState = PeepRideSubState::AtQueueFront;
1703         guest->TimeInQueue = 0;
1704         if (guest->PeepFlags & PEEP_FLAGS_TRACKING)
1705         {
1706             auto ft = Formatter();
1707             guest->FormatNameTo(ft);
1708             ride->FormatNameTo(ft);
1709             if (gConfigNotifications.guest_queuing_for_ride)
1710             {
1711                 News::AddItemToQueue(
1712                     News::ItemType::PeepOnRide, STR_PEEP_TRACKING_PEEP_JOINED_QUEUE_FOR_X, guest->sprite_index, ft);
1713             }
1714         }
1715     }
1716     else
1717     {
1718         // PARK_ENTRANCE
1719         auto* guest = peep->As<Guest>();
1720         if (guest == nullptr)
1721         {
1722             // Staff cannot leave the park, so go back.
1723             peep_return_to_centre_of_tile(peep);
1724             return true;
1725         }
1726 
1727         // If not the centre of the entrance arch
1728         if (tile_element->AsEntrance()->GetSequenceIndex() != 0)
1729         {
1730             peep_return_to_centre_of_tile(guest);
1731             return true;
1732         }
1733 
1734         uint8_t entranceDirection = tile_element->GetDirection();
1735         if (entranceDirection != guest->PeepDirection)
1736         {
1737             if (direction_reverse(entranceDirection) != guest->PeepDirection)
1738             {
1739                 peep_return_to_centre_of_tile(guest);
1740                 return true;
1741             }
1742 
1743             // Peep is leaving the park.
1744             if (guest->State != PeepState::Walking)
1745             {
1746                 peep_return_to_centre_of_tile(guest);
1747                 return true;
1748             }
1749 
1750             if (!(guest->PeepFlags & PEEP_FLAGS_LEAVING_PARK))
1751             {
1752                 // If the park is open and leaving flag isn't set return to centre
1753                 if (gParkFlags & PARK_FLAGS_PARK_OPEN)
1754                 {
1755                     peep_return_to_centre_of_tile(guest);
1756                     return true;
1757                 }
1758             }
1759 
1760             auto destination = guest->GetDestination() + CoordsDirectionDelta[guest->PeepDirection];
1761             guest->SetDestination(destination, 9);
1762             guest->MoveTo({ coords, guest->z });
1763             guest->SetState(PeepState::LeavingPark);
1764 
1765             guest->Var37 = 0;
1766             if (guest->PeepFlags & PEEP_FLAGS_TRACKING)
1767             {
1768                 auto ft = Formatter();
1769                 guest->FormatNameTo(ft);
1770                 if (gConfigNotifications.guest_left_park)
1771                 {
1772                     News::AddItemToQueue(News::ItemType::PeepOnRide, STR_PEEP_TRACKING_LEFT_PARK, guest->sprite_index, ft);
1773                 }
1774             }
1775             return true;
1776         }
1777 
1778         // Peep is entering the park.
1779 
1780         if (guest->State != PeepState::EnteringPark)
1781         {
1782             peep_return_to_centre_of_tile(guest);
1783             return true;
1784         }
1785 
1786         if (!(gParkFlags & PARK_FLAGS_PARK_OPEN))
1787         {
1788             guest->State = PeepState::LeavingPark;
1789             guest->Var37 = 1;
1790             decrement_guests_heading_for_park();
1791             peep_window_state_update(guest);
1792             peep_return_to_centre_of_tile(guest);
1793             return true;
1794         }
1795 
1796         bool found = false;
1797         auto entrance = std::find_if(
1798             gParkEntrances.begin(), gParkEntrances.end(), [coords](const auto& e) { return coords.ToTileStart() == e; });
1799         if (entrance != gParkEntrances.end())
1800         {
1801             int16_t z = entrance->z / 8;
1802             entranceDirection = entrance->direction;
1803             auto nextLoc = coords.ToTileStart() + CoordsDirectionDelta[entranceDirection];
1804 
1805             // Make sure there is a path right behind the entrance, otherwise turn around
1806             TileElement* nextTileElement = map_get_first_element_at(nextLoc);
1807             do
1808             {
1809                 if (nextTileElement == nullptr)
1810                     break;
1811                 if (nextTileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
1812                     continue;
1813 
1814                 if (nextTileElement->AsPath()->IsQueue())
1815                     continue;
1816 
1817                 if (nextTileElement->AsPath()->IsSloped())
1818                 {
1819                     uint8_t slopeDirection = nextTileElement->AsPath()->GetSlopeDirection();
1820                     if (slopeDirection == entranceDirection)
1821                     {
1822                         if (z != nextTileElement->base_height)
1823                         {
1824                             continue;
1825                         }
1826                         found = true;
1827                         break;
1828                     }
1829 
1830                     if (direction_reverse(slopeDirection) != entranceDirection)
1831                         continue;
1832 
1833                     if (z - 2 != nextTileElement->base_height)
1834                         continue;
1835                     found = true;
1836                     break;
1837                 }
1838 
1839                 if (z != nextTileElement->base_height)
1840                 {
1841                     continue;
1842                 }
1843                 found = true;
1844                 break;
1845             } while (!(nextTileElement++)->IsLastForTile());
1846         }
1847 
1848         if (!found)
1849         {
1850             guest->State = PeepState::LeavingPark;
1851             guest->Var37 = 1;
1852             decrement_guests_heading_for_park();
1853             peep_window_state_update(guest);
1854             peep_return_to_centre_of_tile(guest);
1855             return true;
1856         }
1857 
1858         money16 entranceFee = park_get_entrance_fee();
1859         if (entranceFee != 0)
1860         {
1861             if (guest->HasItem(ShopItem::Voucher))
1862             {
1863                 if (guest->VoucherType == VOUCHER_TYPE_PARK_ENTRY_HALF_PRICE)
1864                 {
1865                     entranceFee /= 2;
1866                     guest->RemoveItem(ShopItem::Voucher);
1867                     guest->WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_INVENTORY;
1868                 }
1869                 else if (guest->VoucherType == VOUCHER_TYPE_PARK_ENTRY_FREE)
1870                 {
1871                     entranceFee = 0;
1872                     guest->RemoveItem(ShopItem::Voucher);
1873                     guest->WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_INVENTORY;
1874                 }
1875             }
1876             if (entranceFee > guest->CashInPocket)
1877             {
1878                 guest->State = PeepState::LeavingPark;
1879                 guest->Var37 = 1;
1880                 decrement_guests_heading_for_park();
1881                 peep_window_state_update(guest);
1882                 peep_return_to_centre_of_tile(guest);
1883                 return true;
1884             }
1885 
1886             gTotalIncomeFromAdmissions += entranceFee;
1887             guest->SpendMoney(guest->PaidToEnter, entranceFee, ExpenditureType::ParkEntranceTickets);
1888             guest->PeepFlags |= PEEP_FLAGS_HAS_PAID_FOR_PARK_ENTRY;
1889         }
1890 
1891         gTotalAdmissions++;
1892         window_invalidate_by_number(WC_PARK_INFORMATION, 0);
1893 
1894         guest->Var37 = 1;
1895         auto destination = guest->GetDestination();
1896         destination += CoordsDirectionDelta[guest->PeepDirection];
1897         guest->SetDestination(destination, 7);
1898         guest->MoveTo({ coords, guest->z });
1899     }
1900     return true;
1901 }
1902 
1903 /**
1904  *
1905  *  rct2: 0x006946D8
1906  */
peep_footpath_move_forward(Peep * peep,const CoordsXYE & coords,bool vandalism)1907 static void peep_footpath_move_forward(Peep* peep, const CoordsXYE& coords, bool vandalism)
1908 {
1909     auto tile_element = coords.element;
1910     peep->NextLoc = { coords.ToTileStart(), tile_element->GetBaseZ() };
1911     peep->SetNextFlags(tile_element->AsPath()->GetSlopeDirection(), tile_element->AsPath()->IsSloped(), false);
1912 
1913     int16_t z = peep->GetZOnSlope(coords.x, coords.y);
1914 
1915     auto* guest = peep->As<Guest>();
1916     if (guest == nullptr)
1917     {
1918         peep->MoveTo({ coords, z });
1919         return;
1920     }
1921 
1922     uint8_t vandalThoughtTimeout = (guest->VandalismSeen & 0xC0) >> 6;
1923     // Advance the vandalised tiles by 1
1924     uint8_t vandalisedTiles = (guest->VandalismSeen * 2) & 0x3F;
1925 
1926     if (vandalism)
1927     {
1928         // Add one more to the vandalised tiles
1929         vandalisedTiles |= 1;
1930         // If there has been 2 vandalised tiles in the last 6
1931         if (vandalisedTiles & 0x3E && (vandalThoughtTimeout == 0))
1932         {
1933             if ((scenario_rand() & 0xFFFF) <= 10922)
1934             {
1935                 guest->InsertNewThought(PeepThoughtType::Vandalism);
1936                 guest->HappinessTarget = std::max(0, guest->HappinessTarget - 17);
1937             }
1938             vandalThoughtTimeout = 3;
1939         }
1940     }
1941 
1942     if (vandalThoughtTimeout && (scenario_rand() & 0xFFFF) <= 4369)
1943     {
1944         vandalThoughtTimeout--;
1945     }
1946 
1947     guest->VandalismSeen = (vandalThoughtTimeout << 6) | vandalisedTiles;
1948     uint16_t crowded = 0;
1949     uint8_t litter_count = 0;
1950     uint8_t sick_count = 0;
1951     auto quad = EntityTileList(coords);
1952     for (auto entity : quad)
1953     {
1954         if (auto other_peep = entity->As<Peep>(); other_peep != nullptr)
1955         {
1956             if (other_peep->State != PeepState::Walking)
1957                 continue;
1958 
1959             if (abs(other_peep->z - guest->NextLoc.z) > 16)
1960                 continue;
1961             crowded++;
1962             continue;
1963         }
1964 
1965         if (auto litter = entity->As<Litter>(); litter != nullptr)
1966         {
1967             if (abs(litter->z - guest->NextLoc.z) > 16)
1968                 continue;
1969 
1970             litter_count++;
1971             if (litter->SubType != Litter::Type::Vomit && litter->SubType != Litter::Type::VomitAlt)
1972                 continue;
1973 
1974             litter_count--;
1975             sick_count++;
1976         }
1977     }
1978 
1979     if (crowded >= 10 && guest->State == PeepState::Walking && (scenario_rand() & 0xFFFF) <= 21845)
1980     {
1981         guest->InsertNewThought(PeepThoughtType::Crowded);
1982         guest->HappinessTarget = std::max(0, guest->HappinessTarget - 14);
1983     }
1984 
1985     litter_count = std::min(static_cast<uint8_t>(3), litter_count);
1986     sick_count = std::min(static_cast<uint8_t>(3), sick_count);
1987 
1988     uint8_t disgusting_time = guest->DisgustingCount & 0xC0;
1989     uint8_t disgusting_count = ((guest->DisgustingCount & 0xF) << 2) | sick_count;
1990     guest->DisgustingCount = disgusting_count | disgusting_time;
1991 
1992     if (disgusting_time & 0xC0 && (scenario_rand() & 0xFFFF) <= 4369)
1993     {
1994         // Reduce the disgusting time
1995         guest->DisgustingCount -= 0x40;
1996     }
1997     else
1998     {
1999         uint8_t total_sick = 0;
2000         for (uint8_t time = 0; time < 3; time++)
2001         {
2002             total_sick += (disgusting_count >> (2 * time)) & 0x3;
2003         }
2004 
2005         if (total_sick >= 3 && (scenario_rand() & 0xFFFF) <= 10922)
2006         {
2007             guest->InsertNewThought(PeepThoughtType::PathDisgusting);
2008             guest->HappinessTarget = std::max(0, guest->HappinessTarget - 17);
2009             // Reset disgusting time
2010             guest->DisgustingCount |= 0xC0;
2011         }
2012     }
2013 
2014     uint8_t litter_time = guest->LitterCount & 0xC0;
2015     litter_count = ((guest->LitterCount & 0xF) << 2) | litter_count;
2016     guest->LitterCount = litter_count | litter_time;
2017 
2018     if (litter_time & 0xC0 && (scenario_rand() & 0xFFFF) <= 4369)
2019     {
2020         // Reduce the litter time
2021         guest->LitterCount -= 0x40;
2022     }
2023     else
2024     {
2025         uint8_t total_litter = 0;
2026         for (uint8_t time = 0; time < 3; time++)
2027         {
2028             total_litter += (litter_count >> (2 * time)) & 0x3;
2029         }
2030 
2031         if (total_litter >= 3 && (scenario_rand() & 0xFFFF) <= 10922)
2032         {
2033             guest->InsertNewThought(PeepThoughtType::BadLitter);
2034             guest->HappinessTarget = std::max(0, guest->HappinessTarget - 17);
2035             // Reset litter time
2036             guest->LitterCount |= 0xC0;
2037         }
2038     }
2039 
2040     guest->MoveTo({ coords, z });
2041 }
2042 
2043 /**
2044  *
2045  *  rct2: 0x0069455E
2046  */
peep_interact_with_path(Peep * peep,const CoordsXYE & coords)2047 static void peep_interact_with_path(Peep* peep, const CoordsXYE& coords)
2048 {
2049     // 0x00F1AEE2
2050     auto tile_element = coords.element;
2051     bool vandalism_present = false;
2052     if (tile_element->AsPath()->HasAddition() && (tile_element->AsPath()->IsBroken())
2053         && (tile_element->AsPath()->GetEdges()) != 0xF)
2054     {
2055         vandalism_present = true;
2056     }
2057 
2058     int16_t z = tile_element->GetBaseZ();
2059     auto* guest = peep->As<Guest>();
2060     if (map_is_location_owned({ coords, z }))
2061     {
2062         if (guest != nullptr && guest->OutsideOfPark)
2063         {
2064             peep_return_to_centre_of_tile(guest);
2065             return;
2066         }
2067     }
2068     else
2069     {
2070         if (guest == nullptr || !guest->OutsideOfPark)
2071         {
2072             peep_return_to_centre_of_tile(peep);
2073             return;
2074         }
2075     }
2076 
2077     if (guest != nullptr && tile_element->AsPath()->IsQueue())
2078     {
2079         auto rideIndex = tile_element->AsPath()->GetRideIndex();
2080         if (guest->State == PeepState::Queuing)
2081         {
2082             // Check if this queue is connected to the ride the
2083             // peep is queuing for, i.e. the player hasn't edited
2084             // the queue, rebuilt the ride, etc.
2085             if (guest->CurrentRide == rideIndex)
2086             {
2087                 peep_footpath_move_forward(guest, { coords, tile_element }, vandalism_present);
2088             }
2089             else
2090             {
2091                 // Queue got disconnected from the original ride.
2092                 guest->InteractionRideIndex = RIDE_ID_NULL;
2093                 guest->RemoveFromQueue();
2094                 guest->SetState(PeepState::One);
2095                 peep_footpath_move_forward(guest, { coords, tile_element }, vandalism_present);
2096             }
2097         }
2098         else
2099         {
2100             // Peep is not queuing.
2101             guest->TimeLost = 0;
2102             auto stationNum = tile_element->AsPath()->GetStationIndex();
2103 
2104             if ((tile_element->AsPath()->HasQueueBanner())
2105                 && (tile_element->AsPath()->GetQueueBannerDirection()
2106                     == direction_reverse(guest->PeepDirection)) // Ride sign is facing the direction the peep is walking
2107             )
2108             {
2109                 /* Peep is approaching the entrance of a ride queue.
2110                  * Decide whether to go on the ride. */
2111                 auto ride = get_ride(rideIndex);
2112                 if (ride != nullptr && guest->ShouldGoOnRide(ride, stationNum, true, false))
2113                 {
2114                     // Peep has decided to go on the ride at the queue.
2115                     guest->InteractionRideIndex = rideIndex;
2116 
2117                     // Add the peep to the ride queue.
2118                     uint16_t old_last_peep = ride->stations[stationNum].LastPeepInQueue;
2119                     ride->stations[stationNum].LastPeepInQueue = guest->sprite_index;
2120                     guest->GuestNextInQueue = old_last_peep;
2121                     ride->stations[stationNum].QueueLength++;
2122 
2123                     peep_decrement_num_riders(guest);
2124                     guest->CurrentRide = rideIndex;
2125                     guest->CurrentRideStation = stationNum;
2126                     guest->State = PeepState::Queuing;
2127                     guest->DaysInQueue = 0;
2128                     peep_window_state_update(guest);
2129 
2130                     guest->RideSubState = PeepRideSubState::InQueue;
2131                     guest->DestinationTolerance = 2;
2132                     guest->TimeInQueue = 0;
2133                     if (guest->PeepFlags & PEEP_FLAGS_TRACKING)
2134                     {
2135                         auto ft = Formatter();
2136                         guest->FormatNameTo(ft);
2137                         ride->FormatNameTo(ft);
2138                         if (gConfigNotifications.guest_queuing_for_ride)
2139                         {
2140                             News::AddItemToQueue(
2141                                 News::ItemType::PeepOnRide, STR_PEEP_TRACKING_PEEP_JOINED_QUEUE_FOR_X, guest->sprite_index, ft);
2142                         }
2143                     }
2144 
2145                     peep_footpath_move_forward(guest, { coords, tile_element }, vandalism_present);
2146                 }
2147                 else
2148                 {
2149                     // Peep has decided not to go on the ride.
2150                     peep_return_to_centre_of_tile(guest);
2151                 }
2152             }
2153             else
2154             {
2155                 /* Peep is approaching a queue tile without a ride
2156                  * sign facing the peep. */
2157                 peep_footpath_move_forward(guest, { coords, tile_element }, vandalism_present);
2158             }
2159         }
2160     }
2161     else
2162     {
2163         peep->InteractionRideIndex = RIDE_ID_NULL;
2164         if (guest != nullptr && peep->State == PeepState::Queuing)
2165         {
2166             guest->RemoveFromQueue();
2167             guest->SetState(PeepState::One);
2168         }
2169         peep_footpath_move_forward(peep, { coords, tile_element }, vandalism_present);
2170     }
2171 }
2172 
2173 /**
2174  *
2175  *  rct2: 0x00693F70
2176  */
peep_interact_with_shop(Peep * peep,const CoordsXYE & coords)2177 static bool peep_interact_with_shop(Peep* peep, const CoordsXYE& coords)
2178 {
2179     ride_id_t rideIndex = coords.element->AsTrack()->GetRideIndex();
2180     auto ride = get_ride(rideIndex);
2181     if (ride == nullptr || !ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP))
2182         return false;
2183 
2184     auto* guest = peep->As<Guest>();
2185     if (guest == nullptr)
2186     {
2187         peep_return_to_centre_of_tile(peep);
2188         return true;
2189     }
2190 
2191     // If we are queuing ignore the 'shop'
2192     // This can happen when paths clip through track
2193     if (guest->State == PeepState::Queuing)
2194     {
2195         return false;
2196     }
2197 
2198     guest->TimeLost = 0;
2199 
2200     if (ride->status != RideStatus::Open)
2201     {
2202         peep_return_to_centre_of_tile(guest);
2203         return true;
2204     }
2205 
2206     if (guest->InteractionRideIndex == rideIndex)
2207     {
2208         peep_return_to_centre_of_tile(guest);
2209         return true;
2210     }
2211 
2212     if (guest->PeepFlags & PEEP_FLAGS_LEAVING_PARK)
2213     {
2214         peep_return_to_centre_of_tile(guest);
2215         return true;
2216     }
2217 
2218     if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_PEEP_SHOULD_GO_INSIDE_FACILITY))
2219     {
2220         guest->TimeLost = 0;
2221         if (!guest->ShouldGoOnRide(ride, 0, false, false))
2222         {
2223             peep_return_to_centre_of_tile(guest);
2224             return true;
2225         }
2226 
2227         money16 cost = ride->price[0];
2228         if (cost != 0 && !(gParkFlags & PARK_FLAGS_NO_MONEY))
2229         {
2230             ride->total_profit += cost;
2231             ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_INCOME;
2232             // TODO: Refactor? SpendMoney previously accepted nullptr to not track money, passing a temporary variable as a
2233             // workaround
2234             money16 money = 0;
2235             guest->SpendMoney(money, cost, ExpenditureType::ParkRideTickets);
2236         }
2237 
2238         auto coordsCentre = coords.ToTileCentre();
2239         guest->SetDestination(coordsCentre, 3);
2240         guest->CurrentRide = rideIndex;
2241         guest->SetState(PeepState::EnteringRide);
2242         guest->RideSubState = PeepRideSubState::ApproachShop;
2243 
2244         guest->GuestTimeOnRide = 0;
2245         ride->cur_num_customers++;
2246         if (guest->PeepFlags & PEEP_FLAGS_TRACKING)
2247         {
2248             auto ft = Formatter();
2249             guest->FormatNameTo(ft);
2250             ride->FormatNameTo(ft);
2251             rct_string_id string_id = ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IN_RIDE)
2252                 ? STR_PEEP_TRACKING_PEEP_IS_IN_X
2253                 : STR_PEEP_TRACKING_PEEP_IS_ON_X;
2254             if (gConfigNotifications.guest_used_facility)
2255             {
2256                 News::AddItemToQueue(News::ItemType::PeepOnRide, string_id, guest->sprite_index, ft);
2257             }
2258         }
2259     }
2260     else
2261     {
2262         if (guest->GuestHeadingToRideId == rideIndex)
2263             guest->GuestHeadingToRideId = RIDE_ID_NULL;
2264         guest->ActionSpriteImageOffset = _unk_F1AEF0;
2265         guest->SetState(PeepState::Buying);
2266         guest->CurrentRide = rideIndex;
2267         guest->SubState = 0;
2268     }
2269 
2270     return true;
2271 }
2272 
PerformNextAction(uint8_t & pathing_result)2273 void Peep::PerformNextAction(uint8_t& pathing_result)
2274 {
2275     TileElement* tmpTile;
2276     PerformNextAction(pathing_result, tmpTile);
2277 }
2278 
2279 /**
2280  *
2281  *  rct2: 0x00693C9E
2282  */
PerformNextAction(uint8_t & pathing_result,TileElement * & tile_result)2283 void Peep::PerformNextAction(uint8_t& pathing_result, TileElement*& tile_result)
2284 {
2285     pathing_result = 0;
2286     PeepActionType previousAction = Action;
2287 
2288     if (Action == PeepActionType::Idle)
2289         Action = PeepActionType::Walking;
2290 
2291     auto* guest = As<Guest>();
2292     if (State == PeepState::Queuing && guest != nullptr)
2293     {
2294         if (guest->UpdateQueuePosition(previousAction))
2295             return;
2296     }
2297 
2298     std::optional<CoordsXY> loc;
2299     if (loc = UpdateAction(); !loc.has_value())
2300     {
2301         pathing_result |= PATHING_DESTINATION_REACHED;
2302         uint8_t result = 0;
2303 
2304         if (guest != nullptr)
2305         {
2306             result = guest_path_finding(guest);
2307         }
2308         else
2309         {
2310             auto* staff = As<Staff>();
2311             result = staff->DoPathFinding();
2312         }
2313 
2314         if (result != 0)
2315             return;
2316 
2317         if (loc = UpdateAction(); !loc.has_value())
2318             return;
2319     }
2320 
2321     auto newLoc = *loc;
2322     CoordsXY truncatedNewLoc = newLoc.ToTileStart();
2323     if (truncatedNewLoc == CoordsXY{ NextLoc })
2324     {
2325         int16_t height = GetZOnSlope(newLoc.x, newLoc.y);
2326         MoveTo({ newLoc.x, newLoc.y, height });
2327         return;
2328     }
2329 
2330     if (map_is_edge(newLoc))
2331     {
2332         if (guest != nullptr && guest->OutsideOfPark)
2333         {
2334             pathing_result |= PATHING_OUTSIDE_PARK;
2335         }
2336         peep_return_to_centre_of_tile(this);
2337         return;
2338     }
2339 
2340     TileElement* tileElement = map_get_first_element_at(newLoc);
2341     if (tileElement == nullptr)
2342         return;
2343     int16_t base_z = std::max(0, (z / 8) - 2);
2344     int16_t top_z = (z / 8) + 1;
2345 
2346     do
2347     {
2348         if (base_z > tileElement->base_height)
2349             continue;
2350         if (top_z < tileElement->base_height)
2351             continue;
2352         if (tileElement->IsGhost())
2353             continue;
2354 
2355         if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
2356         {
2357             peep_interact_with_path(this, { newLoc, tileElement });
2358             tile_result = tileElement;
2359             return;
2360         }
2361 
2362         if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
2363         {
2364             if (peep_interact_with_shop(this, { newLoc, tileElement }))
2365             {
2366                 tile_result = tileElement;
2367                 return;
2368             }
2369         }
2370         else if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE)
2371         {
2372             if (peep_interact_with_entrance(this, { newLoc, tileElement }, pathing_result))
2373             {
2374                 tile_result = tileElement;
2375                 return;
2376             }
2377         }
2378     } while (!(tileElement++)->IsLastForTile());
2379 
2380     if (Is<Staff>() || (GetNextIsSurface()))
2381     {
2382         int16_t height = abs(tile_element_height(newLoc) - z);
2383         if (height <= 3 || (Is<Staff>() && height <= 32))
2384         {
2385             InteractionRideIndex = RIDE_ID_NULL;
2386             if (guest != nullptr && State == PeepState::Queuing)
2387             {
2388                 guest->RemoveFromQueue();
2389                 SetState(PeepState::One);
2390             }
2391 
2392             if (!map_is_location_in_park(newLoc))
2393             {
2394                 peep_return_to_centre_of_tile(this);
2395                 return;
2396             }
2397 
2398             auto surfaceElement = map_get_surface_element_at(newLoc);
2399             if (surfaceElement == nullptr)
2400             {
2401                 peep_return_to_centre_of_tile(this);
2402                 return;
2403             }
2404 
2405             int16_t water_height = surfaceElement->GetWaterHeight();
2406             if (water_height > 0)
2407             {
2408                 peep_return_to_centre_of_tile(this);
2409                 return;
2410             }
2411 
2412             auto* staff = As<Staff>();
2413             if (staff != nullptr && !GetNextIsSurface())
2414             {
2415                 // Prevent staff from leaving the path on their own unless they're allowed to mow.
2416                 if (!((staff->StaffOrders & STAFF_ORDERS_MOWING) && staff->StaffMowingTimeout >= 12))
2417                 {
2418                     peep_return_to_centre_of_tile(staff);
2419                     return;
2420                 }
2421             }
2422 
2423             // The peep is on a surface and not on a path
2424             NextLoc = { truncatedNewLoc, surfaceElement->GetBaseZ() };
2425             SetNextFlags(0, false, true);
2426 
2427             height = GetZOnSlope(newLoc.x, newLoc.y);
2428             MoveTo({ newLoc.x, newLoc.y, height });
2429             return;
2430         }
2431     }
2432 
2433     peep_return_to_centre_of_tile(this);
2434 }
2435 
2436 /**
2437  * Gets the height including the bit depending on how far up the slope the peep
2438  * is.
2439  *  rct2: 0x00694921
2440  */
GetZOnSlope(int32_t tile_x,int32_t tile_y)2441 int32_t Peep::GetZOnSlope(int32_t tile_x, int32_t tile_y)
2442 {
2443     if (tile_x == LOCATION_NULL)
2444         return 0;
2445 
2446     if (GetNextIsSurface())
2447     {
2448         return tile_element_height({ tile_x, tile_y });
2449     }
2450 
2451     uint8_t slope = GetNextDirection();
2452     return NextLoc.z + map_height_from_slope({ tile_x, tile_y }, slope, GetNextIsSloped());
2453 }
2454 
get_real_name_string_id_from_id(uint32_t id)2455 rct_string_id get_real_name_string_id_from_id(uint32_t id)
2456 {
2457     // Generate a name_string_idx from the peep Id using bit twiddling
2458     uint16_t ax = static_cast<uint16_t>(id + 0xF0B);
2459     uint16_t dx = 0;
2460     static constexpr uint16_t twiddlingBitOrder[] = {
2461         4, 9, 3, 7, 5, 8, 2, 1, 6, 0, 12, 11, 13, 10,
2462     };
2463     for (size_t i = 0; i < std::size(twiddlingBitOrder); i++)
2464     {
2465         dx |= (ax & (1 << twiddlingBitOrder[i]) ? 1 : 0) << i;
2466     }
2467     ax = dx & 0xF;
2468     dx *= 4;
2469     ax *= 4096;
2470     dx += ax;
2471     if (dx < ax)
2472     {
2473         dx += 0x1000;
2474     }
2475     dx /= 4;
2476     dx += REAL_NAME_START;
2477     return dx;
2478 }
2479 
peep_compare(const uint16_t sprite_index_a,const uint16_t sprite_index_b)2480 int32_t peep_compare(const uint16_t sprite_index_a, const uint16_t sprite_index_b)
2481 {
2482     Peep const* peep_a = GetEntity<Peep>(sprite_index_a);
2483     Peep const* peep_b = GetEntity<Peep>(sprite_index_b);
2484     if (peep_a == nullptr || peep_b == nullptr)
2485     {
2486         return 0;
2487     }
2488 
2489     // Compare types
2490     if (peep_a->Type != peep_b->Type)
2491     {
2492         return static_cast<int32_t>(peep_a->Type) - static_cast<int32_t>(peep_b->Type);
2493     }
2494 
2495     if (peep_a->Name == nullptr && peep_b->Name == nullptr)
2496     {
2497         if (gParkFlags & PARK_FLAGS_SHOW_REAL_GUEST_NAMES)
2498         {
2499             // Potentially could find a more optional way of sorting dynamic real names
2500         }
2501         else
2502         {
2503             // Simple ID comparison for when both peeps use a number or a generated name
2504             return peep_a->Id - peep_b->Id;
2505         }
2506     }
2507 
2508     // Compare their names as strings
2509     char nameA[256]{};
2510     Formatter ft;
2511     peep_a->FormatNameTo(ft);
2512     format_string(nameA, sizeof(nameA), STR_STRINGID, ft.Data());
2513 
2514     char nameB[256]{};
2515     ft.Rewind();
2516     peep_b->FormatNameTo(ft);
2517     format_string(nameB, sizeof(nameB), STR_STRINGID, ft.Data());
2518     return strlogicalcmp(nameA, nameB);
2519 }
2520 
2521 /**
2522  *
2523  *  rct2: 0x0069926C
2524  */
peep_update_names(bool realNames)2525 void peep_update_names(bool realNames)
2526 {
2527     if (realNames)
2528     {
2529         gParkFlags |= PARK_FLAGS_SHOW_REAL_GUEST_NAMES;
2530         // Peep names are now dynamic
2531     }
2532     else
2533     {
2534         gParkFlags &= ~PARK_FLAGS_SHOW_REAL_GUEST_NAMES;
2535         // Peep names are now dynamic
2536     }
2537 
2538     auto intent = Intent(INTENT_ACTION_REFRESH_GUEST_LIST);
2539     context_broadcast_intent(&intent);
2540     gfx_invalidate_screen();
2541 }
2542 
increment_guests_in_park()2543 void increment_guests_in_park()
2544 {
2545     if (gNumGuestsInPark < UINT32_MAX)
2546     {
2547         gNumGuestsInPark++;
2548     }
2549     else
2550     {
2551         openrct2_assert(false, "Attempt to increment guests in park above max value (65535).");
2552     }
2553 }
2554 
increment_guests_heading_for_park()2555 void increment_guests_heading_for_park()
2556 {
2557     if (gNumGuestsHeadingForPark < UINT32_MAX)
2558     {
2559         gNumGuestsHeadingForPark++;
2560     }
2561     else
2562     {
2563         openrct2_assert(false, "Attempt to increment guests heading for park above max value (65535).");
2564     }
2565 }
2566 
decrement_guests_in_park()2567 void decrement_guests_in_park()
2568 {
2569     if (gNumGuestsInPark > 0)
2570     {
2571         gNumGuestsInPark--;
2572     }
2573     else
2574     {
2575         log_error("Attempt to decrement guests in park below zero.");
2576     }
2577 }
2578 
decrement_guests_heading_for_park()2579 void decrement_guests_heading_for_park()
2580 {
2581     if (gNumGuestsHeadingForPark > 0)
2582     {
2583         gNumGuestsHeadingForPark--;
2584     }
2585     else
2586     {
2587         log_error("Attempt to decrement guests heading for park below zero.");
2588     }
2589 }
2590 
peep_release_balloon(Guest * peep,int16_t spawn_height)2591 static void peep_release_balloon(Guest* peep, int16_t spawn_height)
2592 {
2593     if (peep->HasItem(ShopItem::Balloon))
2594     {
2595         peep->RemoveItem(ShopItem::Balloon);
2596 
2597         if (peep->SpriteType == PeepSpriteType::Balloon && peep->x != LOCATION_NULL)
2598         {
2599             Balloon::Create({ peep->x, peep->y, spawn_height }, peep->BalloonColour, false);
2600             peep->WindowInvalidateFlags |= PEEP_INVALIDATE_PEEP_INVENTORY;
2601             peep->UpdateSpriteType();
2602         }
2603     }
2604 }
2605 
2606 /**
2607  *
2608  *  rct2: 0x0069A512
2609  */
RemoveFromRide()2610 void Peep::RemoveFromRide()
2611 {
2612     auto* guest = As<Guest>();
2613     if (guest != nullptr && State == PeepState::Queuing)
2614     {
2615         guest->RemoveFromQueue();
2616     }
2617     StateReset();
2618 }
2619 
SetDestination(const CoordsXY & coords)2620 void Peep::SetDestination(const CoordsXY& coords)
2621 {
2622     DestinationX = static_cast<uint16_t>(coords.x);
2623     DestinationY = static_cast<uint16_t>(coords.y);
2624 }
2625 
SetDestination(const CoordsXY & coords,int32_t tolerance)2626 void Peep::SetDestination(const CoordsXY& coords, int32_t tolerance)
2627 {
2628     SetDestination(coords);
2629     DestinationTolerance = tolerance;
2630 }
2631 
GetDestination() const2632 CoordsXY Peep::GetDestination() const
2633 {
2634     return CoordsXY{ DestinationX, DestinationY };
2635 }
2636