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 "Staff.h"
11 
12 #include "../Context.h"
13 #include "../Game.h"
14 #include "../Input.h"
15 #include "../actions/StaffHireNewAction.h"
16 #include "../actions/StaffSetOrdersAction.h"
17 #include "../audio/audio.h"
18 #include "../config/Config.h"
19 #include "../interface/Viewport.h"
20 #include "../localisation/Date.h"
21 #include "../localisation/Localisation.h"
22 #include "../localisation/StringIds.h"
23 #include "../management/Finance.h"
24 #include "../network/network.h"
25 #include "../object/ObjectList.h"
26 #include "../object/ObjectManager.h"
27 #include "../object/TerrainSurfaceObject.h"
28 #include "../paint/tile_element/Paint.TileElement.h"
29 #include "../ride/RideData.h"
30 #include "../ride/Station.h"
31 #include "../ride/Track.h"
32 #include "../ride/Vehicle.h"
33 #include "../scenario/Scenario.h"
34 #include "../util/Util.h"
35 #include "../windows/Intent.h"
36 #include "../world/Entrance.h"
37 #include "../world/Footpath.h"
38 #include "../world/Scenery.h"
39 #include "../world/SmallScenery.h"
40 #include "../world/Sprite.h"
41 #include "../world/Surface.h"
42 #include "GuestPathfinding.h"
43 #include "Peep.h"
44 
45 #include <algorithm>
46 #include <iterator>
47 
48 // clang-format off
49 const rct_string_id StaffCostumeNames[] = {
50         STR_STAFF_OPTION_COSTUME_PANDA,
51         STR_STAFF_OPTION_COSTUME_TIGER,
52         STR_STAFF_OPTION_COSTUME_ELEPHANT,
53         STR_STAFF_OPTION_COSTUME_ROMAN,
54         STR_STAFF_OPTION_COSTUME_GORILLA,
55         STR_STAFF_OPTION_COSTUME_SNOWMAN,
56         STR_STAFF_OPTION_COSTUME_KNIGHT,
57         STR_STAFF_OPTION_COSTUME_ASTRONAUT,
58         STR_STAFF_OPTION_COSTUME_BANDIT,
59         STR_STAFF_OPTION_COSTUME_SHERIFF,
60         STR_STAFF_OPTION_COSTUME_PIRATE,
61 };
62 // clang-format on
63 
64 uint16_t gStaffDrawPatrolAreas;
65 colour_t gStaffHandymanColour;
66 colour_t gStaffMechanicColour;
67 colour_t gStaffSecurityColour;
68 
69 static PatrolArea _mergedPatrolAreas[EnumValue(StaffType::Count)];
70 
GetMergedPatrolArea(const StaffType type)71 const PatrolArea& GetMergedPatrolArea(const StaffType type)
72 {
73     return _mergedPatrolAreas[EnumValue(type)];
74 }
75 
76 // Maximum manhattan distance that litter can be for a handyman to seek to it
77 const uint16_t MAX_LITTER_DISTANCE = 3 * COORDS_XY_STEP;
78 
Is() const79 template<> bool EntityBase::Is<Staff>() const
80 {
81     return Type == EntityType::Staff;
82 }
83 
84 /**
85  *
86  *  rct2: 0x006BD3A4
87  */
staff_reset_modes()88 void staff_reset_modes()
89 {
90     staff_update_greyed_patrol_areas();
91 }
92 
93 /**
94  * Hires a new staff member of the given type.
95  */
staff_hire_new_member(StaffType staffType,EntertainerCostume entertainerType)96 bool staff_hire_new_member(StaffType staffType, EntertainerCostume entertainerType)
97 {
98     bool autoPosition = gConfigGeneral.auto_staff_placement;
99     if (gInputPlaceObjectModifier & PLACE_OBJECT_MODIFIER_SHIFT_Z)
100     {
101         autoPosition = autoPosition ^ 1;
102     }
103 
104     uint32_t staffOrders = 0;
105 
106     if (staffType == StaffType::Handyman)
107     {
108         staffOrders = STAFF_ORDERS_SWEEPING | STAFF_ORDERS_WATER_FLOWERS | STAFF_ORDERS_EMPTY_BINS;
109         if (gConfigGeneral.handymen_mow_default)
110         {
111             staffOrders |= STAFF_ORDERS_MOWING;
112         }
113     }
114     else if (staffType == StaffType::Mechanic)
115     {
116         staffOrders = STAFF_ORDERS_INSPECT_RIDES | STAFF_ORDERS_FIX_RIDES;
117     }
118 
119     auto hireStaffAction = StaffHireNewAction(autoPosition, staffType, entertainerType, staffOrders);
120     hireStaffAction.SetCallback([=](const GameAction*, const GameActions::Result* res) -> void {
121         if (res->Error != GameActions::Status::Ok)
122             return;
123 
124         auto actionResult = res->GetData<StaffHireNewActionResult>();
125         // Open window for new staff.
126         auto* staff = GetEntity<Staff>(actionResult.StaffEntityId);
127         auto intent = Intent(WC_PEEP);
128         intent.putExtra(INTENT_EXTRA_PEEP, staff);
129         context_open_intent(&intent);
130     });
131 
132     auto res = GameActions::Execute(&hireStaffAction);
133     return res->Error == GameActions::Status::Ok;
134 }
135 
136 /**
137  *
138  *  rct2: 0x006C0C3F
139  */
staff_update_greyed_patrol_areas()140 void staff_update_greyed_patrol_areas()
141 {
142     for (int32_t staffType = 0; staffType < EnumValue(StaffType::Count); ++staffType)
143     {
144         // Reset all of the merged data for the type.
145         auto& mergedData = _mergedPatrolAreas[staffType].Data;
146         std::fill(std::begin(mergedData), std::end(mergedData), 0);
147 
148         for (auto staff : EntityList<Staff>())
149         {
150             if (EnumValue(staff->AssignedStaffType) != staffType)
151             {
152                 continue;
153             }
154             if (!staff->HasPatrolArea())
155             {
156                 continue;
157             }
158 
159             auto staffData = staff->PatrolInfo->Data;
160             for (size_t i = 0; i < STAFF_PATROL_AREA_SIZE; i++)
161             {
162                 mergedData[i] |= staffData[i];
163             }
164         }
165     }
166 }
167 
168 /**
169  *
170  *  rct2: 0x006C0905
171  */
IsLocationInPatrol(const CoordsXY & loc) const172 bool Staff::IsLocationInPatrol(const CoordsXY& loc) const
173 {
174     // Check if location is in the park
175     if (!map_is_location_owned_or_has_rights(loc))
176         return false;
177 
178     // Check if staff has patrol area
179     if (!HasPatrolArea())
180         return true;
181 
182     return IsPatrolAreaSet(loc);
183 }
184 
185 // Check whether the location x,y is inside and on the edge of the
186 // patrol zone for mechanic.
IsLocationOnPatrolEdge(const CoordsXY & loc) const187 bool Staff::IsLocationOnPatrolEdge(const CoordsXY& loc) const
188 {
189     bool onZoneEdge = false;
190     for (uint8_t neighbourDir = 0; !onZoneEdge && neighbourDir <= 7; neighbourDir++)
191     {
192         auto neighbourPos = loc + CoordsDirectionDelta[neighbourDir];
193         onZoneEdge = !IsLocationInPatrol(neighbourPos);
194     }
195     return onZoneEdge;
196 }
197 
CanIgnoreWideFlag(const CoordsXYZ & staffPos,TileElement * path) const198 bool Staff::CanIgnoreWideFlag(const CoordsXYZ& staffPos, TileElement* path) const
199 {
200     /* Wide flags can potentially wall off parts of a staff patrol zone
201      * for the heuristic search.
202      * This function provide doors through such "walls" by defining
203      * the conditions under which staff can ignore the wide path flag. */
204     /* Staff can ignore the wide flag on a path on the edge of the patrol
205      * zone based on its adjacent tiles that are also in the patrol zone
206      * but not on the patrol zone edge:
207      * Basic points of interest are:
208      * - how many such tiles there are;
209      * - whether there are connected paths on those tiles;
210      * - whether the connected paths have the wide flag set.
211      * If there are no such tiles, the path is a concave corner of
212      * the patrol zone and the wide flag can be ignored.
213      * If there is one such tile, the path is on a straight side of the
214      * patrol zone. If this one tile is either a connected wide path or
215      * not a connected path, the wide flag can be ignored.
216      * If there are two such tiles, the path is a convex corner of the
217      * patrol zone. If at most one of these tiles is a connected path or
218      * both of these tiles are connected wide paths, the wide flag can be
219      * ignored. */
220 
221     if (!IsLocationOnPatrolEdge(staffPos))
222     {
223         return false;
224     }
225 
226     /* Check the connected adjacent paths that are also inside the patrol
227      * zone but are not on the patrol zone edge have the wide flag set. */
228     uint8_t total = 0;
229     uint8_t pathcount = 0;
230     uint8_t widecount = 0;
231     for (Direction adjac_dir : ALL_DIRECTIONS)
232     {
233         auto adjacPos = staffPos + CoordsXYZ{ CoordsDirectionDelta[adjac_dir].x, CoordsDirectionDelta[adjac_dir].y, 0 };
234 
235         /* Ignore adjacent tiles outside the patrol zone. */
236         if (!IsLocationInPatrol(adjacPos))
237             continue;
238 
239         /* Ignore adjacent tiles on the patrol zone edge. */
240         if (IsLocationOnPatrolEdge(adjacPos))
241             continue;
242 
243         /* Adjacent tile is inside the patrol zone but not on the
244          * patrol zone edge. */
245         total++;
246 
247         /* Check if path has an edge in adjac_dir */
248         if (!(path->AsPath()->GetEdges() & (1u << adjac_dir)))
249         {
250             continue;
251         }
252 
253         if (path->AsPath()->IsSloped())
254         {
255             if (path->AsPath()->GetSlopeDirection() == adjac_dir)
256             {
257                 adjacPos.z += PATH_HEIGHT_STEP;
258             }
259         }
260 
261         /* Search through all adjacent map elements */
262         TileElement* test_element = map_get_first_element_at(adjacPos);
263         if (test_element == nullptr)
264             return false;
265         bool pathfound = false;
266         bool widefound = false;
267         do
268         {
269             if (test_element->GetType() != TILE_ELEMENT_TYPE_PATH)
270             {
271                 continue;
272             }
273 
274             /* test_element is a path */
275             if (!IsValidPathZAndDirection(test_element, adjacPos.z / COORDS_Z_STEP, adjac_dir))
276                 continue;
277 
278             /* test_element is a connected path */
279             if (!pathfound)
280             {
281                 pathfound = true;
282                 pathcount++;
283             }
284 
285             if (test_element->AsPath()->IsWide())
286             {
287                 if (!widefound)
288                 {
289                     widefound = true;
290                     widecount++;
291                 }
292             }
293         } while (!(test_element++)->IsLastForTile());
294     }
295 
296     switch (total)
297     {
298         case 0: /* Concave corner */
299             return true;
300         case 1: /* Straight side */
301         case 2: /* Convex corner */
302             if (pathcount <= total - 1 || widecount == total)
303             {
304                 return true;
305             }
306     }
307 
308     return false;
309 }
310 
311 /**
312  *
313  *  rct2: 0x006C095B
314  *  returns 0xF if not in a valid patrol area
315  */
GetValidPatrolDirections(const CoordsXY & loc) const316 uint8_t Staff::GetValidPatrolDirections(const CoordsXY& loc) const
317 {
318     uint8_t directions = 0;
319 
320     if (IsLocationInPatrol({ loc.x - COORDS_XY_STEP, loc.y }))
321     {
322         directions |= (1 << 0);
323     }
324 
325     if (IsLocationInPatrol({ loc.x, loc.y + COORDS_XY_STEP }))
326     {
327         directions |= (1 << 1);
328     }
329 
330     if (IsLocationInPatrol({ loc.x + COORDS_XY_STEP, loc.y }))
331     {
332         directions |= (1 << 2);
333     }
334 
335     if (IsLocationInPatrol({ loc.x, loc.y - COORDS_XY_STEP }))
336     {
337         directions |= (1 << 3);
338     }
339 
340     if (directions == 0)
341     {
342         directions = 0xF;
343     }
344 
345     return directions;
346 }
347 
348 /**
349  *
350  *  rct2: 0x006C1955
351  */
ResetStats()352 void Staff::ResetStats()
353 {
354     for (auto peep : EntityList<Staff>())
355     {
356         peep->SetHireDate(gDateMonthsElapsed);
357         peep->StaffLawnsMown = 0;
358         peep->StaffRidesFixed = 0;
359         peep->StaffGardensWatered = 0;
360         peep->StaffRidesInspected = 0;
361         peep->StaffLitterSwept = 0;
362         peep->StaffVandalsStopped = 0;
363         peep->StaffBinsEmptied = 0;
364     }
365 }
366 
getPatrolAreaOffsetIndex(const CoordsXY & coords)367 static std::pair<int32_t, int32_t> getPatrolAreaOffsetIndex(const CoordsXY& coords)
368 {
369     auto tilePos = TileCoordsXY(coords);
370     auto x = tilePos.x / 4;
371     auto y = tilePos.y / 4;
372     auto bitIndex = (y * STAFF_PATROL_AREA_BLOCKS_PER_LINE) + x;
373     auto byteIndex = int32_t(bitIndex / 32);
374     auto byteBitIndex = int32_t(bitIndex % 32);
375     return { byteIndex, byteBitIndex };
376 }
377 
IsPatrolAreaSet(const CoordsXY & coords) const378 bool Staff::IsPatrolAreaSet(const CoordsXY& coords) const
379 {
380     if (PatrolInfo != nullptr)
381     {
382         auto [offset, bitIndex] = getPatrolAreaOffsetIndex(coords);
383         return PatrolInfo->Data[offset] & (1UL << bitIndex);
384     }
385     return false;
386 }
387 
staff_is_patrol_area_set_for_type(StaffType type,const CoordsXY & coords)388 bool staff_is_patrol_area_set_for_type(StaffType type, const CoordsXY& coords)
389 {
390     auto [offset, bitIndex] = getPatrolAreaOffsetIndex(coords);
391     return _mergedPatrolAreas[EnumValue(type)].Data[offset] & (1UL << bitIndex);
392 }
393 
SetPatrolArea(const CoordsXY & coords,bool value)394 void Staff::SetPatrolArea(const CoordsXY& coords, bool value)
395 {
396     if (PatrolInfo == nullptr)
397     {
398         if (value)
399         {
400             PatrolInfo = new PatrolArea();
401         }
402         else
403         {
404             return;
405         }
406     }
407     auto [offset, bitIndex] = getPatrolAreaOffsetIndex(coords);
408     auto* addr = &PatrolInfo->Data[offset];
409     if (value)
410     {
411         *addr |= (1 << bitIndex);
412     }
413     else
414     {
415         *addr &= ~(1 << bitIndex);
416     }
417 }
418 
ClearPatrolArea()419 void Staff::ClearPatrolArea()
420 {
421     delete PatrolInfo;
422     PatrolInfo = nullptr;
423 }
424 
HasPatrolArea() const425 bool Staff::HasPatrolArea() const
426 {
427     if (PatrolInfo == nullptr)
428         return false;
429 
430     constexpr auto hasData = [](const auto& datapoint) { return datapoint != 0; };
431     return std::any_of(std::begin(PatrolInfo->Data), std::end(PatrolInfo->Data), hasData);
432 }
433 
434 /**
435  *
436  *  rct2: 0x006BFBE8
437  *
438  * Returns INVALID_DIRECTION when no nearby litter or unpathable litter
439  */
HandymanDirectionToNearestLitter() const440 Direction Staff::HandymanDirectionToNearestLitter() const
441 {
442     uint16_t nearestLitterDist = 0xFFFF;
443     Litter* nearestLitter = nullptr;
444     for (auto litter : EntityList<Litter>())
445     {
446         uint16_t distance = abs(litter->x - x) + abs(litter->y - y) + abs(litter->z - z) * 4;
447 
448         if (distance < nearestLitterDist)
449         {
450             nearestLitterDist = distance;
451             nearestLitter = litter;
452         }
453     }
454 
455     if (nearestLitterDist > MAX_LITTER_DISTANCE)
456     {
457         return INVALID_DIRECTION;
458     }
459 
460     auto litterTile = CoordsXY{ nearestLitter->x, nearestLitter->y }.ToTileStart();
461 
462     if (!IsLocationInPatrol(litterTile))
463     {
464         return INVALID_DIRECTION;
465     }
466 
467     Direction nextDirection = DirectionFromTo(CoordsXY(x, y), litterTile.ToTileCentre());
468 
469     CoordsXY nextTile = litterTile.ToTileStart() - CoordsDirectionDelta[nextDirection];
470 
471     int16_t nextZ = ((z + COORDS_Z_STEP) & 0xFFF0) / COORDS_Z_STEP;
472 
473     TileElement* tileElement = map_get_first_element_at(nextTile);
474     if (tileElement == nullptr)
475         return INVALID_DIRECTION;
476     do
477     {
478         if (tileElement->base_height != nextZ)
479             continue;
480         if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE || tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
481         {
482             return INVALID_DIRECTION;
483         }
484     } while (!(tileElement++)->IsLastForTile());
485 
486     nextTile = CoordsXY(x, y).ToTileStart() + CoordsDirectionDelta[nextDirection];
487 
488     tileElement = map_get_first_element_at(nextTile);
489     if (tileElement == nullptr)
490         return INVALID_DIRECTION;
491 
492     do
493     {
494         if (tileElement->base_height != nextZ)
495             continue;
496         if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE || tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
497         {
498             return INVALID_DIRECTION;
499         }
500     } while (!(tileElement++)->IsLastForTile());
501 
502     return nextDirection;
503 }
504 
505 /**
506  *
507  *  rct2: 0x006BF931
508  */
HandymanDirectionToUncutGrass(uint8_t valid_directions) const509 uint8_t Staff::HandymanDirectionToUncutGrass(uint8_t valid_directions) const
510 {
511     if (!(GetNextIsSurface()))
512     {
513         auto surfaceElement = map_get_surface_element_at(NextLoc);
514         if (surfaceElement == nullptr)
515             return INVALID_DIRECTION;
516 
517         if (NextLoc.z != surfaceElement->GetBaseZ())
518             return INVALID_DIRECTION;
519 
520         if (GetNextIsSloped())
521         {
522             if (surfaceElement->GetSlope() != PathSlopeToLandSlope[GetNextDirection()])
523                 return INVALID_DIRECTION;
524         }
525         else if (surfaceElement->GetSlope() != TILE_ELEMENT_SLOPE_FLAT)
526             return INVALID_DIRECTION;
527     }
528 
529     uint8_t chosenDirection = scenario_rand() & 0x3;
530     for (uint8_t i = 0; i < 4; ++i, ++chosenDirection)
531     {
532         chosenDirection &= 0x3;
533 
534         if (!(valid_directions & (1 << chosenDirection)))
535         {
536             continue;
537         }
538 
539         CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[chosenDirection];
540 
541         if (!map_is_location_valid(chosenTile))
542             continue;
543 
544         auto surfaceElement = map_get_surface_element_at(chosenTile);
545         if (surfaceElement != nullptr)
546         {
547             if (std::abs(surfaceElement->GetBaseZ() - NextLoc.z) <= 2 * COORDS_Z_STEP)
548             {
549                 if (surfaceElement->CanGrassGrow() && (surfaceElement->GetGrassLength() & 0x7) >= GRASS_LENGTH_CLEAR_1)
550                 {
551                     return chosenDirection;
552                 }
553             }
554         }
555     }
556     return INVALID_DIRECTION;
557 }
558 
559 /**
560  *
561  *  rct2: 0x006BFD9C
562  */
HandymanDirectionRandSurface(uint8_t validDirections) const563 Direction Staff::HandymanDirectionRandSurface(uint8_t validDirections) const
564 {
565     Direction newDirection = scenario_rand() % NumOrthogonalDirections;
566     for (int32_t i = 0; i < NumOrthogonalDirections; ++i, ++newDirection)
567     {
568         newDirection %= NumOrthogonalDirections;
569         if (!(validDirections & (1 << newDirection)))
570             continue;
571 
572         CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
573 
574         if (map_surface_is_blocked(chosenTile))
575             continue;
576 
577         break;
578     }
579     // If it tries all directions this is required
580     // to make it back to the first direction and
581     // override validDirections
582     newDirection %= NumOrthogonalDirections;
583     return newDirection;
584 }
585 
586 /**
587  *
588  *  rct2: 0x006BFBA8
589  */
DoHandymanPathFinding()590 bool Staff::DoHandymanPathFinding()
591 {
592     StaffMowingTimeout++;
593 
594     Direction litterDirection = INVALID_DIRECTION;
595     uint8_t validDirections = GetValidPatrolDirections(NextLoc);
596 
597     if ((StaffOrders & STAFF_ORDERS_SWEEPING) && ((gCurrentTicks + sprite_index) & 0xFFF) > 110)
598     {
599         litterDirection = HandymanDirectionToNearestLitter();
600     }
601 
602     Direction newDirection = INVALID_DIRECTION;
603     if (litterDirection == INVALID_DIRECTION && (StaffOrders & STAFF_ORDERS_MOWING) && StaffMowingTimeout >= 12)
604     {
605         newDirection = HandymanDirectionToUncutGrass(validDirections);
606     }
607 
608     if (newDirection == INVALID_DIRECTION)
609     {
610         if (GetNextIsSurface())
611         {
612             newDirection = HandymanDirectionRandSurface(validDirections);
613         }
614         else
615         {
616             auto* pathElement = map_get_path_element_at(TileCoordsXYZ{ NextLoc });
617 
618             if (pathElement == nullptr)
619                 return true;
620 
621             uint8_t pathDirections = (pathElement->GetEdges() & validDirections) & 0xF;
622             if (pathDirections == 0)
623             {
624                 newDirection = HandymanDirectionRandSurface(validDirections);
625             }
626             else
627             {
628                 bool chooseRandom = true;
629                 if (litterDirection != INVALID_DIRECTION && pathDirections & (1 << litterDirection))
630                 {
631                     /// Check whether path is a queue path and connected to a ride
632                     bool connectedQueue = (pathElement->IsQueue() && pathElement->GetRideIndex() != RIDE_ID_NULL);
633                     /// When in a queue path make the probability of following litter much lower (10% instead of 90%)
634                     /// as handymen often get stuck when there is litter on a normal path next to a queue they are in
635                     uint32_t chooseRandomProbability = connectedQueue ? 0xE666 : 0x1999;
636                     if ((scenario_rand() & 0xFFFF) >= chooseRandomProbability)
637                     {
638                         chooseRandom = false;
639                         newDirection = litterDirection;
640                     }
641                 }
642                 else
643                 {
644                     pathDirections &= ~(1 << direction_reverse(PeepDirection));
645                     if (pathDirections == 0)
646                     {
647                         pathDirections |= 1 << direction_reverse(PeepDirection);
648                     }
649                 }
650 
651                 if (chooseRandom)
652                 {
653                     do
654                     {
655                         newDirection = scenario_rand() & 3;
656                     } while ((pathDirections & (1 << newDirection)) == 0);
657                 }
658             }
659         }
660     }
661 
662     // newDirection can only contain a cardinal direction at this point, no diagonals
663     assert(direction_valid(newDirection));
664 
665     CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
666 
667     while (!map_is_location_valid(chosenTile))
668     {
669         newDirection = HandymanDirectionRandSurface(validDirections);
670         chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
671     }
672 
673     PeepDirection = newDirection;
674     SetDestination(chosenTile + CoordsXY{ 16, 16 }, 3);
675     if (State == PeepState::Queuing)
676     {
677         DestinationTolerance = (scenario_rand() & 7) + 2;
678     }
679     return false;
680 }
681 
DirectionSurface(Direction initialDirection) const682 Direction Staff::DirectionSurface(Direction initialDirection) const
683 {
684     uint8_t direction = initialDirection;
685     for (int32_t i = 0; i < 3; ++i)
686     {
687         // Looks left and right from initial direction
688         switch (i)
689         {
690             case 1:
691                 direction++;
692                 if (scenario_rand() & 1)
693                 {
694                     direction -= 2;
695                 }
696                 break;
697             case 2:
698                 direction -= 2;
699                 break;
700         }
701 
702         direction &= 3;
703 
704         if (fence_in_the_way({ NextLoc, NextLoc.z, NextLoc.z + PEEP_CLEARANCE_HEIGHT }, direction))
705             continue;
706 
707         if (fence_in_the_way({ NextLoc, NextLoc.z, NextLoc.z + PEEP_CLEARANCE_HEIGHT }, direction_reverse(direction)))
708             continue;
709 
710         CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[direction];
711 
712         if (!map_surface_is_blocked(chosenTile))
713         {
714             return direction;
715         }
716     }
717     return initialDirection;
718 }
719 
720 /**
721  *
722  *  rct2: 0x006BFF45
723  */
MechanicDirectionSurface() const724 Direction Staff::MechanicDirectionSurface() const
725 {
726     Direction direction = scenario_rand() & 3;
727 
728     auto ride = get_ride(CurrentRide);
729     if (ride != nullptr && (State == PeepState::Answering || State == PeepState::HeadingToInspection) && (scenario_rand() & 1))
730     {
731         auto location = ride_get_exit_location(ride, CurrentRideStation);
732         if (location.IsNull())
733         {
734             location = ride_get_entrance_location(ride, CurrentRideStation);
735         }
736 
737         direction = DirectionFromTo(CoordsXY(x, y), location.ToCoordsXY());
738     }
739 
740     return DirectionSurface(direction);
741 }
742 
743 /**
744  *
745  *  rct2: 0x006C02D1
746  */
MechanicDirectionPathRand(uint8_t pathDirections) const747 Direction Staff::MechanicDirectionPathRand(uint8_t pathDirections) const
748 {
749     if (scenario_rand() & 1)
750     {
751         if (pathDirections & (1 << PeepDirection))
752             return PeepDirection;
753     }
754 
755     // Modified from original to spam scenario_rand less
756     uint8_t direction = scenario_rand() & 3;
757     for (int32_t i = 0; i < 4; ++i, ++direction)
758     {
759         direction &= 3;
760         if (pathDirections & (1 << direction))
761             return direction;
762     }
763     // This will never happen as pathDirections always has a bit set.
764     return PeepDirection;
765 }
766 
767 /**
768  *
769  *  rct2: 0x006C0121
770  */
MechanicDirectionPath(uint8_t validDirections,PathElement * pathElement)771 Direction Staff::MechanicDirectionPath(uint8_t validDirections, PathElement* pathElement)
772 {
773     uint8_t pathDirections = pathElement->GetEdges();
774     pathDirections &= validDirections;
775 
776     if (pathDirections == 0)
777     {
778         return MechanicDirectionSurface();
779     }
780 
781     // Check if this is dead end - i.e. only way out is the reverse direction.
782     pathDirections &= ~(1 << direction_reverse(PeepDirection));
783     if (pathDirections == 0)
784     {
785         pathDirections |= (1 << direction_reverse(PeepDirection));
786     }
787 
788     Direction direction = bitscanforward(pathDirections);
789     pathDirections &= ~(1 << direction);
790     if (pathDirections == 0)
791     {
792         if (State != PeepState::Answering && State != PeepState::HeadingToInspection)
793         {
794             return direction;
795         }
796 
797         if (SubState != 2)
798         {
799             return direction;
800         }
801         SubState = 3;
802     }
803 
804     pathDirections |= (1 << direction);
805 
806     // Mechanic is heading to ride (either broken down or for inspection).
807     auto ride = get_ride(CurrentRide);
808     if (ride != nullptr && (State == PeepState::Answering || State == PeepState::HeadingToInspection))
809     {
810         /* Find location of the exit for the target ride station
811          * or if the ride has no exit, the entrance. */
812         TileCoordsXYZD location = ride_get_exit_location(ride, CurrentRideStation);
813         if (location.IsNull())
814         {
815             location = ride_get_entrance_location(ride, CurrentRideStation);
816 
817             // If no entrance is present either. This is an incorrect state.
818             if (location.IsNull())
819             {
820                 return MechanicDirectionPathRand(pathDirections);
821             }
822         }
823 
824         gPeepPathFindGoalPosition.x = location.x;
825         gPeepPathFindGoalPosition.y = location.y;
826         gPeepPathFindGoalPosition.z = location.z;
827 
828         gPeepPathFindIgnoreForeignQueues = false;
829         gPeepPathFindQueueRideIndex = RIDE_ID_NULL;
830 
831 #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
832         PathfindLoggingEnable(this);
833 #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
834 
835         Direction pathfindDirection = peep_pathfind_choose_direction(TileCoordsXYZ{ NextLoc }, this);
836 
837 #if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
838         PathfindLoggingDisable();
839 #endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
840 
841         if (pathfindDirection == INVALID_DIRECTION)
842         {
843             /* Heuristic search failed for all directions.
844              * Reset the PathfindGoal - this means that the PathfindHistory
845              * will be reset in the next call to peep_pathfind_choose_direction().
846              * This lets the heuristic search "try again" in case the player has
847              * edited the path layout or the mechanic was already stuck in the
848              * save game (e.g. with a worse version of the pathfinding). */
849             ResetPathfindGoal();
850             return MechanicDirectionPathRand(pathDirections);
851         }
852 
853         return pathfindDirection;
854     }
855     return MechanicDirectionPathRand(pathDirections);
856 }
857 
858 /**
859  *
860  *  rct2: 0x006BFF2C
861  */
DoMechanicPathFinding()862 bool Staff::DoMechanicPathFinding()
863 {
864     uint8_t validDirections = GetValidPatrolDirections(NextLoc);
865     Direction newDirection = INVALID_DIRECTION;
866     if (GetNextIsSurface())
867     {
868         newDirection = MechanicDirectionSurface();
869     }
870     else
871     {
872         auto* pathElement = map_get_path_element_at(TileCoordsXYZ{ NextLoc });
873         if (pathElement == nullptr)
874             return true;
875 
876         newDirection = MechanicDirectionPath(validDirections, pathElement);
877     }
878 
879     // countof(CoordsDirectionDelta)
880     assert(direction_valid(newDirection));
881 
882     CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
883 
884     while (!map_is_location_valid(chosenTile))
885     {
886         newDirection = MechanicDirectionSurface();
887         chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
888     }
889 
890     PeepDirection = newDirection;
891     auto tolerance = (scenario_rand() & 7) + 2;
892     SetDestination(chosenTile + CoordsXY{ 16, 16 }, tolerance);
893 
894     return false;
895 }
896 
897 /**
898  *
899  *  rct2: 0x006C050B
900  */
DirectionPath(uint8_t validDirections,PathElement * pathElement) const901 Direction Staff::DirectionPath(uint8_t validDirections, PathElement* pathElement) const
902 {
903     uint8_t pathDirections = pathElement->GetEdges();
904     if (State != PeepState::Answering && State != PeepState::HeadingToInspection)
905     {
906         pathDirections &= validDirections;
907     }
908 
909     if (pathDirections == 0)
910     {
911         return DirectionSurface(scenario_rand() & 3);
912     }
913 
914     pathDirections &= ~(1 << direction_reverse(PeepDirection));
915     if (pathDirections == 0)
916     {
917         pathDirections |= (1 << direction_reverse(PeepDirection));
918     }
919 
920     Direction direction = bitscanforward(pathDirections);
921     // If this is the only direction they can go, then go
922     if (pathDirections == (1 << direction))
923     {
924         return direction;
925     }
926 
927     direction = scenario_rand() & 3;
928     for (uint8_t i = 0; i < NumOrthogonalDirections; ++i, direction = direction_next(direction))
929     {
930         if (pathDirections & (1 << direction))
931             return direction;
932     }
933 
934     // This will never happen as pathDirections will always have a bit set
935     return direction;
936 }
937 
938 /**
939  *
940  *  rct2: 0x006C0351
941  */
DoMiscPathFinding()942 bool Staff::DoMiscPathFinding()
943 {
944     uint8_t validDirections = GetValidPatrolDirections(NextLoc);
945 
946     Direction newDirection = INVALID_DIRECTION;
947     if (GetNextIsSurface())
948     {
949         newDirection = DirectionSurface(scenario_rand() & 3);
950     }
951     else
952     {
953         auto* pathElement = map_get_path_element_at(TileCoordsXYZ{ NextLoc });
954         if (pathElement == nullptr)
955             return true;
956 
957         newDirection = DirectionPath(validDirections, pathElement);
958     }
959 
960     CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
961 
962     while (!map_is_location_valid(chosenTile))
963     {
964         newDirection = DirectionSurface(scenario_rand() & 3);
965         chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
966     }
967 
968     PeepDirection = newDirection;
969     auto tolerance = (scenario_rand() & 7) + 2;
970     SetDestination(chosenTile + CoordsXY{ 16, 16 }, tolerance);
971 
972     return false;
973 }
974 
975 /**
976  *
977  *  rct2: 0x006C086D
978  */
EntertainerUpdateNearbyPeeps() const979 void Staff::EntertainerUpdateNearbyPeeps() const
980 {
981     for (auto guest : EntityList<Guest>())
982     {
983         if (guest->x == LOCATION_NULL)
984             continue;
985 
986         int16_t z_dist = abs(z - guest->z);
987         if (z_dist > 48)
988             continue;
989 
990         int16_t x_dist = abs(x - guest->x);
991         int16_t y_dist = abs(y - guest->y);
992 
993         if (x_dist > 96)
994             continue;
995 
996         if (y_dist > 96)
997             continue;
998 
999         if (guest->State == PeepState::Walking)
1000         {
1001             guest->HappinessTarget = std::min(guest->HappinessTarget + 4, PEEP_MAX_HAPPINESS);
1002         }
1003         else if (guest->State == PeepState::Queuing)
1004         {
1005             guest->TimeInQueue = std::max(0, guest->TimeInQueue - 200);
1006             guest->HappinessTarget = std::min(guest->HappinessTarget + 3, PEEP_MAX_HAPPINESS);
1007         }
1008     }
1009 }
1010 
1011 /**
1012  *
1013  *  rct2: 0x006C05AE
1014  */
DoEntertainerPathFinding()1015 bool Staff::DoEntertainerPathFinding()
1016 {
1017     if (((scenario_rand() & 0xFFFF) <= 0x4000) && IsActionInterruptable())
1018     {
1019         Action = (scenario_rand() & 1) ? PeepActionType::Wave2 : PeepActionType::Joy;
1020         ActionFrame = 0;
1021         ActionSpriteImageOffset = 0;
1022 
1023         UpdateCurrentActionSpriteType();
1024         EntertainerUpdateNearbyPeeps();
1025     }
1026 
1027     return DoMiscPathFinding();
1028 }
1029 
1030 /**
1031  *
1032  *  rct2: 0x006BF926
1033  */
DoPathFinding()1034 bool Staff::DoPathFinding()
1035 {
1036     switch (AssignedStaffType)
1037     {
1038         case StaffType::Handyman:
1039             return DoHandymanPathFinding();
1040         case StaffType::Mechanic:
1041             return DoMechanicPathFinding();
1042         case StaffType::Security:
1043             return DoMiscPathFinding();
1044         case StaffType::Entertainer:
1045             return DoEntertainerPathFinding();
1046 
1047         default:
1048             assert(false);
1049             return 0;
1050     }
1051 }
1052 
GetCostume() const1053 uint8_t Staff::GetCostume() const
1054 {
1055     return EnumValue(SpriteType) - EnumValue(PeepSpriteType::EntertainerPanda);
1056 }
1057 
SetCostume(uint8_t value)1058 void Staff::SetCostume(uint8_t value)
1059 {
1060     auto costume = static_cast<EntertainerCostume>(value);
1061     SpriteType = EntertainerCostumeToSprite(costume);
1062 }
1063 
SetHireDate(int32_t hireDate)1064 void Staff::SetHireDate(int32_t hireDate)
1065 {
1066     HireDate = hireDate;
1067 }
1068 
GetHireDate() const1069 int32_t Staff::GetHireDate() const
1070 {
1071     return HireDate;
1072 }
1073 
EntertainerCostumeToSprite(EntertainerCostume entertainerType)1074 PeepSpriteType EntertainerCostumeToSprite(EntertainerCostume entertainerType)
1075 {
1076     uint8_t value = static_cast<uint8_t>(entertainerType);
1077     PeepSpriteType newSpriteType = static_cast<PeepSpriteType>(value + EnumValue(PeepSpriteType::EntertainerPanda));
1078     return newSpriteType;
1079 }
1080 
staff_get_colour(StaffType staffType)1081 colour_t staff_get_colour(StaffType staffType)
1082 {
1083     switch (staffType)
1084     {
1085         case StaffType::Handyman:
1086             return gStaffHandymanColour;
1087         case StaffType::Mechanic:
1088             return gStaffMechanicColour;
1089         case StaffType::Security:
1090             return gStaffSecurityColour;
1091         case StaffType::Entertainer:
1092             return 0;
1093         default:
1094             assert(false);
1095             return 0;
1096     }
1097 }
1098 
staff_set_colour(StaffType staffType,colour_t value)1099 bool staff_set_colour(StaffType staffType, colour_t value)
1100 {
1101     switch (staffType)
1102     {
1103         case StaffType::Handyman:
1104             gStaffHandymanColour = value;
1105             break;
1106         case StaffType::Mechanic:
1107             gStaffMechanicColour = value;
1108             break;
1109         case StaffType::Security:
1110             gStaffSecurityColour = value;
1111             break;
1112         default:
1113             return false;
1114     }
1115     return true;
1116 }
1117 
staff_get_available_entertainer_costumes()1118 uint32_t staff_get_available_entertainer_costumes()
1119 {
1120     uint32_t entertainerCostumes = 0;
1121     for (int32_t i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++)
1122     {
1123         if (scenery_group_is_invented(i))
1124         {
1125             const auto sgEntry = get_scenery_group_entry(i);
1126             entertainerCostumes |= sgEntry->entertainer_costumes;
1127         }
1128     }
1129 
1130     // For some reason the flags are +4 from the actual costume IDs
1131     entertainerCostumes >>= 4;
1132 
1133     // Fix #6593: force enable the default costumes, which normally get enabled through the default scenery groups.
1134     entertainerCostumes |= (1 << static_cast<uint8_t>(EntertainerCostume::Panda))
1135         | (1 << static_cast<uint8_t>(EntertainerCostume::Tiger)) | (1 << static_cast<uint8_t>(EntertainerCostume::Elephant));
1136 
1137     return entertainerCostumes;
1138 }
1139 
staff_get_available_entertainer_costume_list(EntertainerCostume * costumeList)1140 int32_t staff_get_available_entertainer_costume_list(EntertainerCostume* costumeList)
1141 {
1142     uint32_t availableCostumes = staff_get_available_entertainer_costumes();
1143     int32_t numCostumes = 0;
1144     for (uint8_t i = 0; i < static_cast<uint8_t>(EntertainerCostume::Count); i++)
1145     {
1146         if (availableCostumes & (1 << i))
1147         {
1148             costumeList[numCostumes++] = static_cast<EntertainerCostume>(i);
1149         }
1150     }
1151     return numCostumes;
1152 }
1153 
1154 /** rct2: 0x009929C8 */
1155 static constexpr const CoordsXY _MowingWaypoints[] = {
1156     { 28, 28 }, { 28, 4 }, { 20, 4 }, { 20, 28 }, { 12, 28 }, { 12, 4 }, { 4, 4 }, { 4, 28 },
1157 };
1158 
1159 /**
1160  *
1161  *  rct2: 0x006BF567
1162  */
UpdateMowing()1163 void Staff::UpdateMowing()
1164 {
1165     if (!CheckForPath())
1166         return;
1167 
1168     while (true)
1169     {
1170         if (auto loc = UpdateAction(); loc.has_value())
1171         {
1172             int16_t checkZ = tile_element_height(*loc);
1173             MoveTo({ loc.value(), checkZ });
1174             return;
1175         }
1176 
1177         Var37++;
1178 
1179         if (Var37 == 1)
1180         {
1181             SwitchToSpecialSprite(2);
1182         }
1183 
1184         if (Var37 == std::size(_MowingWaypoints))
1185         {
1186             StateReset();
1187             return;
1188         }
1189 
1190         auto destination = _MowingWaypoints[Var37] + NextLoc;
1191         SetDestination(destination);
1192 
1193         if (Var37 != 7)
1194             continue;
1195 
1196         auto surfaceElement = map_get_surface_element_at(NextLoc);
1197         if (surfaceElement != nullptr && surfaceElement->CanGrassGrow())
1198         {
1199             surfaceElement->SetGrassLength(GRASS_LENGTH_MOWED);
1200             map_invalidate_tile_zoom0({ NextLoc, surfaceElement->GetBaseZ(), surfaceElement->GetBaseZ() + 16 });
1201         }
1202         StaffLawnsMown++;
1203         WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
1204     }
1205 }
1206 
1207 /**
1208  *
1209  *  rct2: 0x006BF7E6
1210  */
UpdateWatering()1211 void Staff::UpdateWatering()
1212 {
1213     StaffMowingTimeout = 0;
1214     if (SubState == 0)
1215     {
1216         if (!CheckForPath())
1217             return;
1218 
1219         uint8_t pathingResult;
1220         PerformNextAction(pathingResult);
1221         if (!(pathingResult & PATHING_DESTINATION_REACHED))
1222             return;
1223 
1224         sprite_direction = (Var37 & 3) << 3;
1225         Action = PeepActionType::StaffWatering;
1226         ActionFrame = 0;
1227         ActionSpriteImageOffset = 0;
1228         UpdateCurrentActionSpriteType();
1229 
1230         SubState = 1;
1231     }
1232     else if (SubState == 1)
1233     {
1234         if (!IsActionWalking())
1235         {
1236             UpdateAction();
1237             Invalidate();
1238             return;
1239         }
1240 
1241         auto actionLoc = CoordsXY{ NextLoc } + CoordsDirectionDelta[Var37];
1242 
1243         TileElement* tile_element = map_get_first_element_at(actionLoc);
1244         if (tile_element == nullptr)
1245             return;
1246 
1247         do
1248         {
1249             if (tile_element->GetType() != TILE_ELEMENT_TYPE_SMALL_SCENERY)
1250                 continue;
1251 
1252             if (abs(NextLoc.z - tile_element->GetBaseZ()) > 4 * COORDS_Z_STEP)
1253                 continue;
1254 
1255             auto* sceneryEntry = tile_element->AsSmallScenery()->GetEntry();
1256 
1257             if (!sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_CAN_BE_WATERED))
1258                 continue;
1259 
1260             tile_element->AsSmallScenery()->SetAge(0);
1261             map_invalidate_tile_zoom0({ actionLoc, tile_element->GetBaseZ(), tile_element->GetClearanceZ() });
1262             StaffGardensWatered++;
1263             WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
1264         } while (!(tile_element++)->IsLastForTile());
1265 
1266         StateReset();
1267     }
1268 }
1269 
1270 /**
1271  *
1272  *  rct2: 0x006BF6C9
1273  */
UpdateEmptyingBin()1274 void Staff::UpdateEmptyingBin()
1275 {
1276     StaffMowingTimeout = 0;
1277 
1278     if (SubState == 0)
1279     {
1280         if (!CheckForPath())
1281             return;
1282 
1283         uint8_t pathingResult;
1284         PerformNextAction(pathingResult);
1285         if (!(pathingResult & PATHING_DESTINATION_REACHED))
1286             return;
1287 
1288         sprite_direction = (Var37 & 3) << 3;
1289         Action = PeepActionType::StaffEmptyBin;
1290         ActionFrame = 0;
1291         ActionSpriteImageOffset = 0;
1292         UpdateCurrentActionSpriteType();
1293 
1294         SubState = 1;
1295     }
1296     else if (SubState == 1)
1297     {
1298         if (IsActionWalking())
1299         {
1300             StateReset();
1301             return;
1302         }
1303 
1304         UpdateAction();
1305         Invalidate();
1306 
1307         if (ActionFrame != 11)
1308             return;
1309 
1310         TileElement* tile_element = map_get_first_element_at(NextLoc);
1311         if (tile_element == nullptr)
1312             return;
1313 
1314         for (;; tile_element++)
1315         {
1316             if (tile_element->GetType() == TILE_ELEMENT_TYPE_PATH)
1317             {
1318                 if (NextLoc.z == tile_element->GetBaseZ())
1319                     break;
1320             }
1321             if ((tile_element)->IsLastForTile())
1322             {
1323                 StateReset();
1324                 return;
1325             }
1326         }
1327 
1328         if (!tile_element->AsPath()->HasAddition())
1329         {
1330             StateReset();
1331             return;
1332         }
1333 
1334         auto* pathAddEntry = tile_element->AsPath()->GetAdditionEntry();
1335         if (!(pathAddEntry->flags & PATH_BIT_FLAG_IS_BIN) || tile_element->AsPath()->IsBroken()
1336             || tile_element->AsPath()->AdditionIsGhost())
1337         {
1338             StateReset();
1339             return;
1340         }
1341 
1342         uint8_t additionStatus = tile_element->AsPath()->GetAdditionStatus() | ((3 << Var37) << Var37);
1343         tile_element->AsPath()->SetAdditionStatus(additionStatus);
1344 
1345         map_invalidate_tile_zoom0({ NextLoc, tile_element->GetBaseZ(), tile_element->GetClearanceZ() });
1346         StaffBinsEmptied++;
1347         WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
1348     }
1349 }
1350 
1351 /**
1352  *
1353  *  rct2: 0x6BF641
1354  */
UpdateSweeping()1355 void Staff::UpdateSweeping()
1356 {
1357     StaffMowingTimeout = 0;
1358     if (!CheckForPath())
1359         return;
1360 
1361     if (Action == PeepActionType::StaffSweep && ActionFrame == 8)
1362     {
1363         // Remove sick at this location
1364         Litter::RemoveAt(GetLocation());
1365         StaffLitterSwept++;
1366         WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
1367     }
1368     if (auto loc = UpdateAction(); loc.has_value())
1369     {
1370         int16_t actionZ = GetZOnSlope((*loc).x, (*loc).y);
1371         MoveTo({ loc.value(), actionZ });
1372         return;
1373     }
1374 
1375     Var37++;
1376     if (Var37 != 2)
1377     {
1378         Action = PeepActionType::StaffSweep;
1379         ActionFrame = 0;
1380         ActionSpriteImageOffset = 0;
1381         UpdateCurrentActionSpriteType();
1382         return;
1383     }
1384     StateReset();
1385 }
1386 
1387 /**
1388  *
1389  *  rct2: 0x006C16D7
1390  */
UpdateHeadingToInspect()1391 void Staff::UpdateHeadingToInspect()
1392 {
1393     auto ride = get_ride(CurrentRide);
1394     if (ride == nullptr)
1395     {
1396         SetState(PeepState::Falling);
1397         return;
1398     }
1399 
1400     if (ride_get_exit_location(ride, CurrentRideStation).IsNull())
1401     {
1402         ride->lifecycle_flags &= ~RIDE_LIFECYCLE_DUE_INSPECTION;
1403         SetState(PeepState::Falling);
1404         return;
1405     }
1406 
1407     if (ride->mechanic_status != RIDE_MECHANIC_STATUS_HEADING || !(ride->lifecycle_flags & RIDE_LIFECYCLE_DUE_INSPECTION))
1408     {
1409         SetState(PeepState::Falling);
1410         return;
1411     }
1412 
1413     if (SubState == 0)
1414     {
1415         MechanicTimeSinceCall = 0;
1416         ResetPathfindGoal();
1417         SubState = 2;
1418     }
1419 
1420     if (SubState <= 3)
1421     {
1422         MechanicTimeSinceCall++;
1423         if (MechanicTimeSinceCall > 2500)
1424         {
1425             if (ride->lifecycle_flags & RIDE_LIFECYCLE_DUE_INSPECTION && ride->mechanic_status == RIDE_MECHANIC_STATUS_HEADING)
1426             {
1427                 ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
1428             }
1429             SetState(PeepState::Falling);
1430             return;
1431         }
1432 
1433         if (!CheckForPath())
1434             return;
1435 
1436         uint8_t pathingResult;
1437         TileElement* rideEntranceExitElement;
1438         PerformNextAction(pathingResult, rideEntranceExitElement);
1439 
1440         if (!(pathingResult & PATHING_RIDE_EXIT) && !(pathingResult & PATHING_RIDE_ENTRANCE))
1441         {
1442             return;
1443         }
1444 
1445         if (CurrentRide != rideEntranceExitElement->AsEntrance()->GetRideIndex())
1446             return;
1447 
1448         uint8_t exit_index = rideEntranceExitElement->AsEntrance()->GetStationIndex();
1449 
1450         if (CurrentRideStation != exit_index)
1451             return;
1452 
1453         if (pathingResult & PATHING_RIDE_ENTRANCE)
1454         {
1455             if (!ride_get_exit_location(ride, exit_index).IsNull())
1456             {
1457                 return;
1458             }
1459         }
1460 
1461         PeepDirection = rideEntranceExitElement->GetDirection();
1462 
1463         auto newDestination = CoordsXY{ 16, 16 } + NextLoc + (DirectionOffsets[PeepDirection] * 53);
1464         SetDestination(newDestination, 2);
1465         sprite_direction = PeepDirection << 3;
1466 
1467         z = rideEntranceExitElement->base_height * 4;
1468         SubState = 4;
1469         // Falls through into SubState 4
1470     }
1471 
1472     int16_t delta_y = abs(GetLocation().y - GetDestination().y);
1473     if (auto loc = UpdateAction(); loc.has_value())
1474     {
1475         int32_t newZ = ride->stations[CurrentRideStation].GetBaseZ();
1476 
1477         if (delta_y < 20)
1478         {
1479             newZ += ride->GetRideTypeDescriptor().Heights.PlatformHeight;
1480         }
1481 
1482         MoveTo({ loc.value(), newZ });
1483         return;
1484     }
1485 
1486     SetState(PeepState::Inspecting);
1487     SubState = 0;
1488 }
1489 
1490 /**
1491  *
1492  *  rct2: 0x006C0CB8
1493  */
UpdateAnswering()1494 void Staff::UpdateAnswering()
1495 {
1496     auto ride = get_ride(CurrentRide);
1497     if (ride == nullptr || ride->mechanic_status != RIDE_MECHANIC_STATUS_HEADING)
1498     {
1499         SetState(PeepState::Falling);
1500         return;
1501     }
1502 
1503     if (SubState == 0)
1504     {
1505         Action = PeepActionType::StaffAnswerCall;
1506         ActionFrame = 0;
1507         ActionSpriteImageOffset = 0;
1508 
1509         UpdateCurrentActionSpriteType();
1510 
1511         SubState = 1;
1512         peep_window_state_update(this);
1513         return;
1514     }
1515     if (SubState == 1)
1516     {
1517         if (IsActionWalking())
1518         {
1519             SubState = 2;
1520             peep_window_state_update(this);
1521             MechanicTimeSinceCall = 0;
1522             ResetPathfindGoal();
1523             return;
1524         }
1525         UpdateAction();
1526         Invalidate();
1527         return;
1528     }
1529     if (SubState <= 3)
1530     {
1531         MechanicTimeSinceCall++;
1532         if (MechanicTimeSinceCall > 2500)
1533         {
1534             ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
1535             ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
1536             SetState(PeepState::Falling);
1537             return;
1538         }
1539 
1540         if (!CheckForPath())
1541             return;
1542 
1543         uint8_t pathingResult;
1544         TileElement* rideEntranceExitElement;
1545         PerformNextAction(pathingResult, rideEntranceExitElement);
1546 
1547         if (!(pathingResult & PATHING_RIDE_EXIT) && !(pathingResult & PATHING_RIDE_ENTRANCE))
1548         {
1549             return;
1550         }
1551 
1552         if (CurrentRide != rideEntranceExitElement->AsEntrance()->GetRideIndex())
1553             return;
1554 
1555         uint8_t exit_index = rideEntranceExitElement->AsEntrance()->GetStationIndex();
1556 
1557         if (CurrentRideStation != exit_index)
1558             return;
1559 
1560         if (pathingResult & PATHING_RIDE_ENTRANCE)
1561         {
1562             if (!ride_get_exit_location(ride, exit_index).IsNull())
1563             {
1564                 return;
1565             }
1566         }
1567 
1568         PeepDirection = rideEntranceExitElement->GetDirection();
1569 
1570         int32_t destX = NextLoc.x + 16 + DirectionOffsets[PeepDirection].x * 53;
1571         int32_t destY = NextLoc.y + 16 + DirectionOffsets[PeepDirection].y * 53;
1572 
1573         SetDestination({ destX, destY }, 2);
1574         sprite_direction = PeepDirection << 3;
1575 
1576         z = rideEntranceExitElement->base_height * 4;
1577         SubState = 4;
1578         // Falls through into SubState 4
1579     }
1580 
1581     int16_t delta_y = abs(y - GetDestination().y);
1582     if (auto loc = UpdateAction(); loc.has_value())
1583     {
1584         int32_t newZ = ride->stations[CurrentRideStation].GetBaseZ();
1585 
1586         if (delta_y < 20)
1587         {
1588             newZ += ride->GetRideTypeDescriptor().Heights.PlatformHeight;
1589         }
1590 
1591         MoveTo({ loc.value(), newZ });
1592         return;
1593     }
1594 
1595     SetState(PeepState::Fixing);
1596     SubState = 0;
1597 }
1598 
1599 /** rct2: 0x00992A5C */
1600 static constexpr const CoordsXY _WateringUseOffsets[] = {
1601     { 3, 16 }, { 16, 29 }, { 29, 16 }, { 16, 3 }, { 3, 29 }, { 29, 29 }, { 29, 3 }, { 3, 3 },
1602 };
1603 
1604 /**
1605  *
1606  *  rct2: 0x006BF483
1607  */
UpdatePatrollingFindWatering()1608 bool Staff::UpdatePatrollingFindWatering()
1609 {
1610     if (!(StaffOrders & STAFF_ORDERS_WATER_FLOWERS))
1611         return false;
1612 
1613     uint8_t chosen_position = scenario_rand() & 7;
1614     for (int32_t i = 0; i < 8; ++i, ++chosen_position)
1615     {
1616         chosen_position &= 7;
1617 
1618         auto chosenLoc = CoordsXY{ NextLoc } + CoordsDirectionDelta[chosen_position];
1619 
1620         TileElement* tile_element = map_get_first_element_at(chosenLoc);
1621 
1622         // This seems to happen in some SV4 files.
1623         if (tile_element == nullptr)
1624         {
1625             continue;
1626         }
1627 
1628         do
1629         {
1630             if (tile_element->GetType() != TILE_ELEMENT_TYPE_SMALL_SCENERY)
1631             {
1632                 continue;
1633             }
1634 
1635             auto z_diff = abs(NextLoc.z - tile_element->GetBaseZ());
1636 
1637             if (z_diff >= 4 * COORDS_Z_STEP)
1638             {
1639                 continue;
1640             }
1641 
1642             auto* sceneryEntry = tile_element->AsSmallScenery()->GetEntry();
1643 
1644             if (sceneryEntry == nullptr || !sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_CAN_BE_WATERED))
1645             {
1646                 continue;
1647             }
1648 
1649             if (tile_element->AsSmallScenery()->GetAge() < SCENERY_WITHER_AGE_THRESHOLD_2)
1650             {
1651                 if (chosen_position >= 4)
1652                 {
1653                     continue;
1654                 }
1655 
1656                 if (tile_element->AsSmallScenery()->GetAge() < SCENERY_WITHER_AGE_THRESHOLD_1)
1657                 {
1658                     continue;
1659                 }
1660             }
1661 
1662             SetState(PeepState::Watering);
1663             Var37 = chosen_position;
1664 
1665             SubState = 0;
1666             auto destination = _WateringUseOffsets[chosen_position] + GetLocation().ToTileStart();
1667             SetDestination(destination, 3);
1668 
1669             return true;
1670         } while (!(tile_element++)->IsLastForTile());
1671     }
1672     return false;
1673 }
1674 
1675 /**
1676  *
1677  *  rct2: 0x006BF3A1
1678  */
UpdatePatrollingFindBin()1679 bool Staff::UpdatePatrollingFindBin()
1680 {
1681     if (!(StaffOrders & STAFF_ORDERS_EMPTY_BINS))
1682         return false;
1683 
1684     if (GetNextIsSurface())
1685         return false;
1686 
1687     TileElement* tileElement = map_get_first_element_at(NextLoc);
1688     if (tileElement == nullptr)
1689         return false;
1690 
1691     for (;; tileElement++)
1692     {
1693         if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH && (tileElement->GetBaseZ() == NextLoc.z))
1694             break;
1695 
1696         if (tileElement->IsLastForTile())
1697             return false;
1698     }
1699 
1700     if (!tileElement->AsPath()->HasAddition())
1701         return false;
1702     auto* pathAddEntry = tileElement->AsPath()->GetAdditionEntry();
1703     if (pathAddEntry == nullptr)
1704         return false;
1705 
1706     if (!(pathAddEntry->flags & PATH_BIT_FLAG_IS_BIN))
1707         return false;
1708 
1709     if (tileElement->AsPath()->IsBroken())
1710         return false;
1711 
1712     if (tileElement->AsPath()->AdditionIsGhost())
1713         return false;
1714 
1715     uint8_t bin_positions = tileElement->AsPath()->GetEdges();
1716     uint8_t bin_quantity = tileElement->AsPath()->GetAdditionStatus();
1717     uint8_t chosen_position = 0;
1718 
1719     for (; chosen_position < 4; ++chosen_position)
1720     {
1721         if (!(bin_positions & 1) && !(bin_quantity & 3))
1722             break;
1723         bin_positions >>= 1;
1724         bin_quantity >>= 2;
1725     }
1726 
1727     if (chosen_position == 4)
1728         return false;
1729 
1730     Var37 = chosen_position;
1731     SetState(PeepState::EmptyingBin);
1732 
1733     SubState = 0;
1734     auto destination = BinUseOffsets[chosen_position] + GetLocation().ToTileStart();
1735     SetDestination(destination, 3);
1736     return true;
1737 }
1738 
1739 /**
1740  *
1741  *  rct2: 0x006BF322
1742  */
UpdatePatrollingFindGrass()1743 bool Staff::UpdatePatrollingFindGrass()
1744 {
1745     if (!(StaffOrders & STAFF_ORDERS_MOWING))
1746         return false;
1747 
1748     if (StaffMowingTimeout < 12)
1749         return false;
1750 
1751     if (!(GetNextIsSurface()))
1752         return false;
1753 
1754     auto surfaceElement = map_get_surface_element_at(NextLoc);
1755     if (surfaceElement != nullptr && surfaceElement->CanGrassGrow())
1756     {
1757         if ((surfaceElement->GetGrassLength() & 0x7) >= GRASS_LENGTH_CLEAR_1)
1758         {
1759             SetState(PeepState::Mowing);
1760             Var37 = 0;
1761             // Original code used .y for both x and y. Changed to .x to make more sense (both x and y are 28)
1762 
1763             auto destination = _MowingWaypoints[0] + NextLoc;
1764             SetDestination(destination, 3);
1765             return true;
1766         }
1767     }
1768     return false;
1769 }
1770 
1771 /**
1772  *
1773  *  rct2: 0x006BF295
1774  */
UpdatePatrollingFindSweeping()1775 bool Staff::UpdatePatrollingFindSweeping()
1776 {
1777     if (!(StaffOrders & STAFF_ORDERS_SWEEPING))
1778         return false;
1779     auto quad = EntityTileList<Litter>({ x, y });
1780     for (auto litter : quad)
1781     {
1782         uint16_t z_diff = abs(z - litter->z);
1783 
1784         if (z_diff >= 16)
1785             continue;
1786 
1787         SetState(PeepState::Sweeping);
1788 
1789         Var37 = 0;
1790         SetDestination(litter->GetLocation(), 5);
1791         return true;
1792     }
1793 
1794     return false;
1795 }
1796 
Tick128UpdateStaff()1797 void Staff::Tick128UpdateStaff()
1798 {
1799     if (AssignedStaffType != StaffType::Security)
1800         return;
1801 
1802     PeepSpriteType newSpriteType = PeepSpriteType::SecurityAlt;
1803     if (State != PeepState::Patrolling)
1804         newSpriteType = PeepSpriteType::Security;
1805 
1806     if (SpriteType == newSpriteType)
1807         return;
1808 
1809     SpriteType = newSpriteType;
1810     ActionSpriteImageOffset = 0;
1811     WalkingFrameNum = 0;
1812     if (Action < PeepActionType::Idle)
1813         Action = PeepActionType::Walking;
1814 
1815     PeepFlags &= ~PEEP_FLAGS_SLOW_WALK;
1816     if (gSpriteTypeToSlowWalkMap[EnumValue(newSpriteType)])
1817     {
1818         PeepFlags |= PEEP_FLAGS_SLOW_WALK;
1819     }
1820 
1821     ActionSpriteType = PeepActionSpriteType::Invalid;
1822     UpdateCurrentActionSpriteType();
1823 }
1824 
IsMechanic() const1825 bool Staff::IsMechanic() const
1826 {
1827     return AssignedStaffType == StaffType::Mechanic;
1828 }
1829 
UpdateStaff(uint32_t stepsToTake)1830 void Staff::UpdateStaff(uint32_t stepsToTake)
1831 {
1832     switch (State)
1833     {
1834         case PeepState::Patrolling:
1835             UpdatePatrolling();
1836             break;
1837         case PeepState::Mowing:
1838             UpdateMowing();
1839             break;
1840         case PeepState::Sweeping:
1841             UpdateSweeping();
1842             break;
1843         case PeepState::Answering:
1844             UpdateAnswering();
1845             break;
1846         case PeepState::Fixing:
1847             UpdateFixing(stepsToTake);
1848             break;
1849         case PeepState::Inspecting:
1850             UpdateFixing(stepsToTake);
1851             break;
1852         case PeepState::EmptyingBin:
1853             UpdateEmptyingBin();
1854             break;
1855         case PeepState::Watering:
1856             UpdateWatering();
1857             break;
1858         case PeepState::HeadingToInspection:
1859             UpdateHeadingToInspect();
1860             break;
1861         default:
1862             // TODO reset to default state
1863             assert(false);
1864             break;
1865     }
1866 }
1867 
1868 /**
1869  *
1870  *  rct2: 0x006BF1FD
1871  */
UpdatePatrolling()1872 void Staff::UpdatePatrolling()
1873 {
1874     if (!CheckForPath())
1875         return;
1876 
1877     uint8_t pathingResult;
1878     PerformNextAction(pathingResult);
1879     if (!(pathingResult & PATHING_DESTINATION_REACHED))
1880         return;
1881 
1882     if (GetNextIsSurface())
1883     {
1884         auto surfaceElement = map_get_surface_element_at(NextLoc);
1885 
1886         if (surfaceElement != nullptr)
1887         {
1888             int32_t water_height = surfaceElement->GetWaterHeight();
1889             if (water_height > 0)
1890             {
1891                 MoveTo({ x, y, water_height });
1892                 SetState(PeepState::Falling);
1893                 return;
1894             }
1895         }
1896     }
1897 
1898     if (AssignedStaffType != StaffType::Handyman)
1899         return;
1900 
1901     if (UpdatePatrollingFindSweeping())
1902         return;
1903 
1904     if (UpdatePatrollingFindGrass())
1905         return;
1906 
1907     if (UpdatePatrollingFindBin())
1908         return;
1909 
1910     UpdatePatrollingFindWatering();
1911 }
1912 
1913 enum
1914 {
1915     PEEP_FIXING_ENTER_STATION = 0,
1916     PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE = 1,
1917     PEEP_FIXING_FIX_VEHICLE_CLOSED_RESTRAINTS = 2,
1918     PEEP_FIXING_FIX_VEHICLE_CLOSED_DOORS = 3,
1919     PEEP_FIXING_FIX_VEHICLE_OPEN_RESTRAINTS = 4,
1920     PEEP_FIXING_FIX_VEHICLE_OPEN_DOORS = 5,
1921     PEEP_FIXING_FIX_VEHICLE_MALFUNCTION = 6,
1922     PEEP_FIXING_MOVE_TO_STATION_END = 7,
1923     PEEP_FIXING_FIX_STATION_END = 8,
1924     PEEP_FIXING_MOVE_TO_STATION_START = 9,
1925     PEEP_FIXING_FIX_STATION_START = 10,
1926     PEEP_FIXING_FIX_STATION_BRAKES = 11,
1927     PEEP_FIXING_MOVE_TO_STATION_EXIT = 12,
1928     PEEP_FIXING_FINISH_FIX_OR_INSPECT = 13,
1929     PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT = 14,
1930 };
1931 
1932 /**
1933  * FixingSubstatesForBreakdown[] defines the applicable peep sub_states for
1934  * mechanics fixing a ride. The array is indexed by breakdown_reason:
1935  * - indexes 0-7 are the 8 breakdown reasons (see BREAKDOWN_* in Ride.h)
1936  *   when fixing a broken down ride;
1937  * - index 8 is for inspecting a ride.
1938  */
1939 // clang-format off
1940 static constexpr const uint32_t FixingSubstatesForBreakdown[9] = {
1941     ( // BREAKDOWN_SAFETY_CUT_OUT
1942         (1 << PEEP_FIXING_MOVE_TO_STATION_END) |
1943         (1 << PEEP_FIXING_FIX_STATION_END) |
1944         (1 << PEEP_FIXING_MOVE_TO_STATION_START) |
1945         (1 << PEEP_FIXING_FIX_STATION_START) |
1946         (1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
1947         (1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
1948         (1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
1949     ),
1950     ( // BREAKDOWN_RESTRAINTS_STUCK_CLOSED
1951         (1 << PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE) |
1952         (1 << PEEP_FIXING_FIX_VEHICLE_CLOSED_RESTRAINTS) |
1953         (1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
1954         (1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
1955         (1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
1956     ),
1957     ( // BREAKDOWN_RESTRAINTS_STUCK_OPEN
1958         (1 << PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE) |
1959         (1 << PEEP_FIXING_FIX_VEHICLE_OPEN_RESTRAINTS) |
1960         (1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
1961         (1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
1962         (1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
1963     ),
1964     ( // BREAKDOWN_DOORS_STUCK_CLOSED
1965         (1 << PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE) |
1966         (1 << PEEP_FIXING_FIX_VEHICLE_CLOSED_DOORS) |
1967         (1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
1968         (1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
1969         (1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
1970     ),
1971     ( // BREAKDOWN_DOORS_STUCK_OPEN
1972         (1 << PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE) |
1973         (1 << PEEP_FIXING_FIX_VEHICLE_OPEN_DOORS) |
1974         (1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
1975         (1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
1976         (1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
1977     ),
1978     ( // BREAKDOWN_VEHICLE_MALFUNCTION
1979         (1 << PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE) |
1980         (1 << PEEP_FIXING_FIX_VEHICLE_MALFUNCTION) |
1981         (1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
1982         (1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
1983         (1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
1984     ),
1985     ( // BREAKDOWN_BRAKES_FAILURE
1986         (1 << PEEP_FIXING_MOVE_TO_STATION_START) |
1987         (1 << PEEP_FIXING_FIX_STATION_BRAKES) |
1988         (1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
1989         (1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
1990         (1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
1991     ),
1992     ( // BREAKDOWN_CONTROL_FAILURE
1993         (1 << PEEP_FIXING_MOVE_TO_STATION_END) |
1994         (1 << PEEP_FIXING_FIX_STATION_END) |
1995         (1 << PEEP_FIXING_MOVE_TO_STATION_START) |
1996         (1 << PEEP_FIXING_FIX_STATION_START) |
1997         (1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
1998         (1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
1999         (1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
2000     ),
2001     ( // INSPECTION
2002         (1 << PEEP_FIXING_MOVE_TO_STATION_END) |
2003         (1 << PEEP_FIXING_FIX_STATION_END) |
2004         (1 << PEEP_FIXING_MOVE_TO_STATION_START) |
2005         (1 << PEEP_FIXING_FIX_STATION_START) |
2006         (1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
2007         (1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
2008         (1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
2009     ),
2010 };
2011 // clang-format on
2012 
2013 /**
2014  *
2015  *  rct2: 0x006C0E8B
2016  * Also used by inspecting.
2017  */
UpdateFixing(int32_t steps)2018 void Staff::UpdateFixing(int32_t steps)
2019 {
2020     auto ride = get_ride(CurrentRide);
2021     if (ride == nullptr)
2022     {
2023         SetState(PeepState::Falling);
2024         return;
2025     }
2026 
2027     bool progressToNextSubstate = true;
2028     bool firstRun = true;
2029 
2030     if ((State == PeepState::Inspecting)
2031         && (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN)))
2032     {
2033         // Ride has broken down since Mechanic was called to inspect it.
2034         // Mechanic identifies the breakdown and switches to fixing it.
2035         State = PeepState::Fixing;
2036     }
2037 
2038     while (progressToNextSubstate)
2039     {
2040         switch (SubState)
2041         {
2042             case PEEP_FIXING_ENTER_STATION:
2043                 NextFlags &= ~PEEP_NEXT_FLAG_IS_SLOPED;
2044                 progressToNextSubstate = UpdateFixingEnterStation(ride);
2045                 break;
2046 
2047             case PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE:
2048                 progressToNextSubstate = UpdateFixingMoveToBrokenDownVehicle(firstRun, ride);
2049                 break;
2050 
2051             case PEEP_FIXING_FIX_VEHICLE_CLOSED_RESTRAINTS:
2052             case PEEP_FIXING_FIX_VEHICLE_CLOSED_DOORS:
2053             case PEEP_FIXING_FIX_VEHICLE_OPEN_RESTRAINTS:
2054             case PEEP_FIXING_FIX_VEHICLE_OPEN_DOORS:
2055                 progressToNextSubstate = UpdateFixingFixVehicle(firstRun, ride);
2056                 break;
2057 
2058             case PEEP_FIXING_FIX_VEHICLE_MALFUNCTION:
2059                 progressToNextSubstate = UpdateFixingFixVehicleMalfunction(firstRun, ride);
2060                 break;
2061 
2062             case PEEP_FIXING_MOVE_TO_STATION_END:
2063                 progressToNextSubstate = UpdateFixingMoveToStationEnd(firstRun, ride);
2064                 break;
2065 
2066             case PEEP_FIXING_FIX_STATION_END:
2067                 progressToNextSubstate = UpdateFixingFixStationEnd(firstRun);
2068                 break;
2069 
2070             case PEEP_FIXING_MOVE_TO_STATION_START:
2071                 progressToNextSubstate = UpdateFixingMoveToStationStart(firstRun, ride);
2072                 break;
2073 
2074             case PEEP_FIXING_FIX_STATION_START:
2075                 progressToNextSubstate = UpdateFixingFixStationStart(firstRun, ride);
2076                 break;
2077 
2078             case PEEP_FIXING_FIX_STATION_BRAKES:
2079                 progressToNextSubstate = UpdateFixingFixStationBrakes(firstRun, ride);
2080                 break;
2081 
2082             case PEEP_FIXING_MOVE_TO_STATION_EXIT:
2083                 progressToNextSubstate = UpdateFixingMoveToStationExit(firstRun, ride);
2084                 break;
2085 
2086             case PEEP_FIXING_FINISH_FIX_OR_INSPECT:
2087                 progressToNextSubstate = UpdateFixingFinishFixOrInspect(firstRun, steps, ride);
2088                 break;
2089 
2090             case PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT:
2091                 progressToNextSubstate = UpdateFixingLeaveByEntranceExit(firstRun, ride);
2092                 break;
2093 
2094             default:
2095                 log_error("Invalid substate");
2096                 progressToNextSubstate = false;
2097         }
2098 
2099         firstRun = false;
2100 
2101         if (!progressToNextSubstate)
2102         {
2103             break;
2104         }
2105 
2106         int32_t subState = SubState;
2107         uint32_t sub_state_sequence_mask = FixingSubstatesForBreakdown[8];
2108 
2109         if (State != PeepState::Inspecting)
2110         {
2111             sub_state_sequence_mask = FixingSubstatesForBreakdown[ride->breakdown_reason_pending];
2112         }
2113 
2114         do
2115         {
2116             subState++;
2117         } while ((sub_state_sequence_mask & (1 << subState)) == 0);
2118 
2119         SubState = subState & 0xFF;
2120     }
2121 }
2122 
2123 /**
2124  * rct2: 0x006C0EEC
2125  * fixing SubState: enter_station - applies to fixing all break down reasons and ride inspections.
2126  */
UpdateFixingEnterStation(Ride * ride) const2127 bool Staff::UpdateFixingEnterStation(Ride* ride) const
2128 {
2129     ride->mechanic_status = RIDE_MECHANIC_STATUS_FIXING;
2130     ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
2131 
2132     return true;
2133 }
2134 
2135 /**
2136  * rct2: 0x006C0F09
2137  * fixing SubState: move_to_broken_down_vehicle - applies to fixing all vehicle specific breakdown reasons
2138  * - see FixingSubstatesForBreakdown[]
2139  */
UpdateFixingMoveToBrokenDownVehicle(bool firstRun,const Ride * ride)2140 bool Staff::UpdateFixingMoveToBrokenDownVehicle(bool firstRun, const Ride* ride)
2141 {
2142     if (!firstRun)
2143     {
2144         Vehicle* vehicle = ride_get_broken_vehicle(ride);
2145         if (vehicle == nullptr)
2146         {
2147             return true;
2148         }
2149 
2150         while (true)
2151         {
2152             if (vehicle->IsHead())
2153             {
2154                 break;
2155             }
2156 
2157             auto trackType = vehicle->GetTrackType();
2158             if (track_type_is_station(trackType))
2159             {
2160                 break;
2161             }
2162 
2163             vehicle = GetEntity<Vehicle>(vehicle->prev_vehicle_on_ride);
2164             if (vehicle == nullptr)
2165             {
2166                 return true;
2167             }
2168         }
2169 
2170         CoordsXY offset = DirectionOffsets[PeepDirection];
2171         auto destination = (offset * -12) + vehicle->GetLocation();
2172         SetDestination(destination, 2);
2173     }
2174 
2175     if (auto loc = UpdateAction(); loc.has_value())
2176     {
2177         MoveTo({ loc.value(), z });
2178         return false;
2179     }
2180 
2181     return true;
2182 }
2183 
2184 /**
2185  * rct2: 0x006C0FD3
2186  * fixing SubState: fix_vehicle - applies to fixing vehicle with:
2187  * 1. restraints stuck closed,
2188  * 2. doors stuck closed,
2189  * 3. restrains stuck open,
2190  * 4. doors stuck open.
2191  * - see FixingSubstatesForBreakdown[]
2192  */
UpdateFixingFixVehicle(bool firstRun,const Ride * ride)2193 bool Staff::UpdateFixingFixVehicle(bool firstRun, const Ride* ride)
2194 {
2195     if (!firstRun)
2196     {
2197         sprite_direction = PeepDirection << 3;
2198 
2199         Action = (scenario_rand() & 1) ? PeepActionType::StaffFix2 : PeepActionType::StaffFix;
2200         ActionSpriteImageOffset = 0;
2201         ActionFrame = 0;
2202         UpdateCurrentActionSpriteType();
2203     }
2204 
2205     if (IsActionWalking())
2206     {
2207         return true;
2208     }
2209 
2210     UpdateAction();
2211     Invalidate();
2212 
2213     uint8_t actionFrame = (Action == PeepActionType::StaffFix) ? 0x25 : 0x50;
2214     if (ActionFrame != actionFrame)
2215     {
2216         return false;
2217     }
2218 
2219     Vehicle* vehicle = ride_get_broken_vehicle(ride);
2220     if (vehicle == nullptr)
2221     {
2222         return true;
2223     }
2224 
2225     vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_CAR);
2226 
2227     return false;
2228 }
2229 
2230 /**
2231  * rct2: 0x006C107B
2232  * fixing SubState: fix_vehicle_malfunction - applies fixing to vehicle malfunction.
2233  * - see FixingSubstatesForBreakdown[]
2234  */
UpdateFixingFixVehicleMalfunction(bool firstRun,const Ride * ride)2235 bool Staff::UpdateFixingFixVehicleMalfunction(bool firstRun, const Ride* ride)
2236 {
2237     if (!firstRun)
2238     {
2239         sprite_direction = PeepDirection << 3;
2240         Action = PeepActionType::StaffFix3;
2241         ActionSpriteImageOffset = 0;
2242         ActionFrame = 0;
2243 
2244         UpdateCurrentActionSpriteType();
2245     }
2246 
2247     if (IsActionWalking())
2248     {
2249         return true;
2250     }
2251 
2252     UpdateAction();
2253     Invalidate();
2254 
2255     if (ActionFrame != 0x65)
2256     {
2257         return false;
2258     }
2259 
2260     Vehicle* vehicle = ride_get_broken_vehicle(ride);
2261     if (vehicle == nullptr)
2262     {
2263         return true;
2264     }
2265 
2266     vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_TRAIN);
2267 
2268     return false;
2269 }
2270 
2271 /** rct2: 0x00992A3C */
2272 static constexpr const CoordsXY _StationFixingOffsets[] = {
2273     { -12, 0 },
2274     { 0, 12 },
2275     { 12, 0 },
2276     { 0, -12 },
2277 };
2278 
2279 /**
2280  * rct2: 0x006C1114
2281  * fixing SubState: move_to_station_end - applies to fixing station specific breakdowns: safety cut-out, control failure,
2282  * inspection.
2283  * - see FixingSubstatesForBreakdown[]
2284  */
UpdateFixingMoveToStationEnd(bool firstRun,const Ride * ride)2285 bool Staff::UpdateFixingMoveToStationEnd(bool firstRun, const Ride* ride)
2286 {
2287     if (!firstRun)
2288     {
2289         if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION | RIDE_TYPE_FLAG_HAS_NO_TRACK))
2290         {
2291             return true;
2292         }
2293 
2294         auto stationPos = ride->stations[CurrentRideStation].GetStart();
2295         if (stationPos.IsNull())
2296         {
2297             return true;
2298         }
2299 
2300         auto tileElement = map_get_track_element_at(stationPos);
2301         if (tileElement == nullptr)
2302         {
2303             log_error("Couldn't find tile_element");
2304             return false;
2305         }
2306 
2307         int32_t trackDirection = tileElement->GetDirection();
2308         CoordsXY offset = _StationFixingOffsets[trackDirection];
2309 
2310         stationPos.x += 16 + offset.x;
2311         if (offset.x == 0)
2312         {
2313             stationPos.x = GetDestination().x;
2314         }
2315 
2316         stationPos.y += 16 + offset.y;
2317         if (offset.y == 0)
2318         {
2319             stationPos.y = GetDestination().y;
2320         }
2321 
2322         SetDestination(stationPos, 2);
2323     }
2324 
2325     if (auto loc = UpdateAction(); loc.has_value())
2326     {
2327         MoveTo({ loc.value(), z });
2328         return false;
2329     }
2330 
2331     return true;
2332 }
2333 
2334 /**
2335  * rct2: 0x006C11F5
2336  * fixing SubState: fix_station_end - applies to fixing station specific breakdowns: safety cut-out, control failure,
2337  * inspection.
2338  * - see FixingSubstatesForBreakdown[]
2339  */
UpdateFixingFixStationEnd(bool firstRun)2340 bool Staff::UpdateFixingFixStationEnd(bool firstRun)
2341 {
2342     if (!firstRun)
2343     {
2344         sprite_direction = PeepDirection << 3;
2345         Action = PeepActionType::StaffCheckboard;
2346         ActionFrame = 0;
2347         ActionSpriteImageOffset = 0;
2348 
2349         UpdateCurrentActionSpriteType();
2350     }
2351 
2352     if (IsActionWalking())
2353     {
2354         return true;
2355     }
2356 
2357     UpdateAction();
2358     Invalidate();
2359 
2360     return false;
2361 }
2362 
2363 /**
2364  * rct2: 0x006C1239
2365  * fixing SubState: move_to_station_start
2366  * 1. applies to fixing station specific breakdowns: safety cut-out, control failure,
2367  * 2. applies to fixing brake failure,
2368  * 3. applies to inspection.
2369  * - see FixingSubstatesForBreakdown[]
2370  */
UpdateFixingMoveToStationStart(bool firstRun,const Ride * ride)2371 bool Staff::UpdateFixingMoveToStationStart(bool firstRun, const Ride* ride)
2372 {
2373     if (!firstRun)
2374     {
2375         if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION | RIDE_TYPE_FLAG_HAS_NO_TRACK))
2376         {
2377             return true;
2378         }
2379 
2380         auto stationPosition = ride->stations[CurrentRideStation].GetStart();
2381         if (stationPosition.IsNull())
2382         {
2383             return true;
2384         }
2385 
2386         CoordsXYE input;
2387         input.x = stationPosition.x;
2388         input.y = stationPosition.y;
2389         input.element = map_get_track_element_at_from_ride({ input.x, input.y, stationPosition.z }, CurrentRide);
2390         if (input.element == nullptr)
2391         {
2392             return true;
2393         }
2394 
2395         Direction stationDirection = 0;
2396         track_begin_end trackBeginEnd;
2397         while (track_block_get_previous(input, &trackBeginEnd))
2398         {
2399             if (trackBeginEnd.begin_element->AsTrack()->IsStation())
2400             {
2401                 input.x = trackBeginEnd.begin_x;
2402                 input.y = trackBeginEnd.begin_y;
2403                 input.element = trackBeginEnd.begin_element;
2404 
2405                 stationDirection = trackBeginEnd.begin_element->GetDirection();
2406                 continue;
2407             }
2408 
2409             break;
2410         }
2411 
2412         // loc_6C12ED:
2413         auto destination = CoordsXY{ input.x + 16, input.y + 16 };
2414         auto offset = _StationFixingOffsets[stationDirection];
2415 
2416         destination.x -= offset.x;
2417         if (offset.x == 0)
2418         {
2419             destination.x = GetDestination().x;
2420         }
2421 
2422         destination.y -= offset.y;
2423         if (offset.y == 0)
2424         {
2425             destination.y = GetDestination().y;
2426         }
2427 
2428         SetDestination(destination, 2);
2429     }
2430 
2431     if (auto loc = UpdateAction(); loc.has_value())
2432     {
2433         MoveTo({ loc.value(), z });
2434         return false;
2435     }
2436 
2437     return true;
2438 }
2439 
2440 /**
2441  * rct2: 0x006C1368
2442  * fixing SubState: fix_station_start
2443  * 1. applies to fixing station specific breakdowns: safety cut-out, control failure,
2444  * 2. applies to inspection.
2445  * - see FixingSubstatesForBreakdown[]
2446  */
UpdateFixingFixStationStart(bool firstRun,const Ride * ride)2447 bool Staff::UpdateFixingFixStationStart(bool firstRun, const Ride* ride)
2448 {
2449     if (!firstRun)
2450     {
2451         if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION | RIDE_TYPE_FLAG_HAS_NO_TRACK))
2452         {
2453             return true;
2454         }
2455 
2456         sprite_direction = PeepDirection << 3;
2457 
2458         Action = PeepActionType::StaffFix;
2459         ActionFrame = 0;
2460         ActionSpriteImageOffset = 0;
2461 
2462         UpdateCurrentActionSpriteType();
2463     }
2464 
2465     if (IsActionWalking())
2466     {
2467         return true;
2468     }
2469 
2470     UpdateAction();
2471 
2472     return false;
2473 }
2474 
2475 /**
2476  * rct2: 0x006C13CE
2477  * fixing SubState: fix_station_brakes - applies to fixing brake failure
2478  * - see FixingSubstatesForBreakdown[]
2479  */
UpdateFixingFixStationBrakes(bool firstRun,Ride * ride)2480 bool Staff::UpdateFixingFixStationBrakes(bool firstRun, Ride* ride)
2481 {
2482     if (!firstRun)
2483     {
2484         sprite_direction = PeepDirection << 3;
2485 
2486         Action = PeepActionType::StaffFixGround;
2487         ActionFrame = 0;
2488         ActionSpriteImageOffset = 0;
2489 
2490         UpdateCurrentActionSpriteType();
2491     }
2492 
2493     if (IsActionWalking())
2494     {
2495         return true;
2496     }
2497 
2498     UpdateAction();
2499     Invalidate();
2500 
2501     if (ActionFrame == 0x28)
2502     {
2503         ride->mechanic_status = RIDE_MECHANIC_STATUS_HAS_FIXED_STATION_BRAKES;
2504         ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
2505     }
2506 
2507     if (ActionFrame == 0x13 || ActionFrame == 0x19 || ActionFrame == 0x1F || ActionFrame == 0x25 || ActionFrame == 0x2B)
2508     {
2509         OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::MechanicFix, GetLocation());
2510     }
2511 
2512     return false;
2513 }
2514 
2515 /**
2516  * rct2: 0x006C1474
2517  * fixing SubState: move_to_station_exit - applies to fixing all failures & inspections
2518  * - see FixingSubstatesForBreakdown[]
2519  */
UpdateFixingMoveToStationExit(bool firstRun,const Ride * ride)2520 bool Staff::UpdateFixingMoveToStationExit(bool firstRun, const Ride* ride)
2521 {
2522     if (!firstRun)
2523     {
2524         auto stationPosition = ride_get_exit_location(ride, CurrentRideStation).ToCoordsXY();
2525         if (stationPosition.IsNull())
2526         {
2527             stationPosition = ride_get_entrance_location(ride, CurrentRideStation).ToCoordsXY();
2528 
2529             if (stationPosition.IsNull())
2530             {
2531                 return true;
2532             }
2533         }
2534 
2535         stationPosition = stationPosition.ToTileCentre();
2536 
2537         CoordsXY stationPlatformDirection = DirectionOffsets[PeepDirection];
2538         stationPosition.x += stationPlatformDirection.x * 20;
2539         stationPosition.y += stationPlatformDirection.y * 20;
2540 
2541         SetDestination(stationPosition, 2);
2542     }
2543 
2544     if (auto loc = UpdateAction(); loc.has_value())
2545     {
2546         MoveTo({ loc.value(), z });
2547         return false;
2548     }
2549 
2550     return true;
2551 }
2552 
2553 /**
2554  * rct2: 0x006C1504
2555  * fixing SubState: finish_fix_or_inspect - applies to fixing all failures & inspections
2556  * - see FixingSubstatesForBreakdown[]
2557  */
UpdateFixingFinishFixOrInspect(bool firstRun,int32_t steps,Ride * ride)2558 bool Staff::UpdateFixingFinishFixOrInspect(bool firstRun, int32_t steps, Ride* ride)
2559 {
2560     if (!firstRun)
2561     {
2562         if (State == PeepState::Inspecting)
2563         {
2564             UpdateRideInspected(CurrentRide);
2565 
2566             StaffRidesInspected++;
2567             WindowInvalidateFlags |= RIDE_INVALIDATE_RIDE_INCOME | RIDE_INVALIDATE_RIDE_LIST;
2568             ride->mechanic_status = RIDE_MECHANIC_STATUS_UNDEFINED;
2569             return true;
2570         }
2571 
2572         StaffRidesFixed++;
2573         WindowInvalidateFlags |= RIDE_INVALIDATE_RIDE_INCOME | RIDE_INVALIDATE_RIDE_LIST;
2574 
2575         sprite_direction = PeepDirection << 3;
2576         Action = PeepActionType::StaffAnswerCall2;
2577         ActionFrame = 0;
2578         ActionSpriteImageOffset = 0;
2579 
2580         UpdateCurrentActionSpriteType();
2581     }
2582 
2583     if (!IsActionWalking())
2584     {
2585         UpdateAction();
2586         Invalidate();
2587         return false;
2588     }
2589 
2590     ride_fix_breakdown(ride, steps);
2591     ride->mechanic_status = RIDE_MECHANIC_STATUS_UNDEFINED;
2592     return true;
2593 }
2594 
2595 /**
2596  * rct2: 0x006C157E
2597  * fixing SubState: leave_by_entrance_exit - applies to fixing all failures & inspections
2598  * - see FixingSubstatesForBreakdown[]
2599  */
UpdateFixingLeaveByEntranceExit(bool firstRun,const Ride * ride)2600 bool Staff::UpdateFixingLeaveByEntranceExit(bool firstRun, const Ride* ride)
2601 {
2602     if (!firstRun)
2603     {
2604         auto exitPosition = ride_get_exit_location(ride, CurrentRideStation).ToCoordsXY();
2605         if (exitPosition.IsNull())
2606         {
2607             exitPosition = ride_get_entrance_location(ride, CurrentRideStation).ToCoordsXY();
2608 
2609             if (exitPosition.IsNull())
2610             {
2611                 SetState(PeepState::Falling);
2612                 return false;
2613             }
2614         }
2615 
2616         exitPosition = exitPosition.ToTileCentre();
2617 
2618         CoordsXY ebx_direction = DirectionOffsets[PeepDirection];
2619         exitPosition.x -= ebx_direction.x * 19;
2620         exitPosition.y -= ebx_direction.y * 19;
2621 
2622         SetDestination(exitPosition, 2);
2623     }
2624 
2625     int16_t xy_distance;
2626     if (auto loc = UpdateAction(xy_distance); loc.has_value())
2627     {
2628         uint16_t stationHeight = ride->stations[CurrentRideStation].GetBaseZ();
2629 
2630         if (xy_distance >= 16)
2631         {
2632             stationHeight += ride->GetRideTypeDescriptor().Heights.PlatformHeight;
2633         }
2634 
2635         MoveTo({ loc.value(), stationHeight });
2636         return false;
2637     }
2638     SetState(PeepState::Falling);
2639     return false;
2640 }
2641 
2642 /**
2643  * rct2: 0x6B7588
2644  */
UpdateRideInspected(ride_id_t rideIndex)2645 void Staff::UpdateRideInspected(ride_id_t rideIndex)
2646 {
2647     auto ride = get_ride(rideIndex);
2648     if (ride != nullptr)
2649     {
2650         ride->lifecycle_flags &= ~RIDE_LIFECYCLE_DUE_INSPECTION;
2651         ride->reliability += ((100 - ride->reliability_percentage) / 4) * (scenario_rand() & 0xFF);
2652         ride->last_inspection = 0;
2653         ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE | RIDE_INVALIDATE_RIDE_MAIN
2654             | RIDE_INVALIDATE_RIDE_LIST;
2655     }
2656 }
2657 
GetStaffWage(StaffType type)2658 money32 GetStaffWage(StaffType type)
2659 {
2660     switch (type)
2661     {
2662         default:
2663         case StaffType::Handyman:
2664             return MONEY(50, 00);
2665         case StaffType::Mechanic:
2666             return MONEY(80, 00);
2667         case StaffType::Security:
2668             return MONEY(60, 00);
2669         case StaffType::Entertainer:
2670             return MONEY(55, 00);
2671     }
2672 }
2673