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