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