OpenRCT2/src/openrct2/entity/Staff.cpp

2596 lines
70 KiB
C++
Raw Normal View History

/*****************************************************************************
* Copyright (c) 2014-2024 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
2018-06-22 23:04:19 +02:00
#include "Staff.h"
#include "../Context.h"
2017-11-30 18:17:06 +01:00
#include "../Game.h"
#include "../GameState.h"
2017-12-12 14:52:57 +01:00
#include "../Input.h"
Split actions hpp files into separate h and cpp files (#13548) * Split up SmallSceneryPlace/Remove Added undo function for Remove Scenery * Refactor: Balloon and Banner actions hpp=>h/cpp * Refactor: rename all action *.hpp files to *.cpp This is preparation for separation in later commits. Note that without the complete set of commits in this branch, the code will not build. * Refactor Clear, Climate, Custom, and Footpath actions hpp=>h/cpp * VSCode: add src subdirectories to includePath * Refactor Guest actions hpp=>h/cpp * Refactor Land actions hpp=>h/cpp * Refactor LargeScenery actions hpp=>h/cpp * Refactor Load, Maze, Network actions hpp=>h/cpp * Refactor Park actions hpp=>h/cpp * Refactor/style: move private function declarations in actions *.h Previous action .h files included private function declarations with private member variables, before public function declarations. This commit re-orders the header files to the following order: - public member variables - private member variables - public functions - private functions * Refactor Pause action hpp=>h/cpp * Refactor Peep, Place, Player actions hpp=>h/cpp * Refactor Ride actions hpp=>h/cpp * Refactor Scenario, Set*, Sign* actions hpp=>h/cpp * Refactor SmallScenerySetColourAction hpp=>h/cpp * Refactor Staff actions hpp=>h/cpp * Refactor Surface, Tile, Track* actions hpp=>h/cpp * Refactor Wall and Water actions hpp=>h/cpp * Fix various includes and other compile errors Update includes for tests. Move static function declarations to .h files Add explicit includes to various files that were previously implicit (the required header was a nested include in an action hpp file, and the action .h file does not include that header) Move RideSetStatus string enum to the cpp file to avoid unused imports * Xcode: modify project file for actions refactor * Cleanup whitespace and end-of-file newlines Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2020-12-10 07:39:10 +01:00
#include "../actions/StaffSetOrdersAction.h"
2018-06-22 23:04:19 +02:00
#include "../audio/audio.h"
#include "../config/Config.h"
#include "../core/DataSerialiser.h"
2021-11-24 15:58:01 +01:00
#include "../entity/EntityRegistry.h"
2018-01-06 00:45:53 +01:00
#include "../interface/Viewport.h"
2018-01-06 18:32:25 +01:00
#include "../localisation/Date.h"
#include "../localisation/Localisation.h"
#include "../localisation/StringIds.h"
2017-10-06 22:37:06 +02:00
#include "../management/Finance.h"
#include "../network/network.h"
#include "../object/ObjectEntryManager.h"
2018-01-02 20:36:42 +01:00
#include "../object/ObjectList.h"
#include "../object/ObjectManager.h"
#include "../object/PathAdditionEntry.h"
#include "../object/SceneryGroupEntry.h"
#include "../object/SmallSceneryEntry.h"
#include "../object/TerrainSurfaceObject.h"
2018-06-22 23:04:19 +02:00
#include "../paint/tile_element/Paint.TileElement.h"
2021-11-25 22:47:24 +01:00
#include "../peep/GuestPathfinding.h"
2018-04-02 19:00:36 +02:00
#include "../ride/RideData.h"
2018-06-22 23:04:19 +02:00
#include "../ride/Station.h"
2018-04-03 19:17:01 +02:00
#include "../ride/Track.h"
#include "../ride/Vehicle.h"
2018-01-02 18:58:43 +01:00
#include "../scenario/Scenario.h"
2017-12-13 13:02:24 +01:00
#include "../util/Util.h"
Split actions hpp files into separate h and cpp files (#13548) * Split up SmallSceneryPlace/Remove Added undo function for Remove Scenery * Refactor: Balloon and Banner actions hpp=>h/cpp * Refactor: rename all action *.hpp files to *.cpp This is preparation for separation in later commits. Note that without the complete set of commits in this branch, the code will not build. * Refactor Clear, Climate, Custom, and Footpath actions hpp=>h/cpp * VSCode: add src subdirectories to includePath * Refactor Guest actions hpp=>h/cpp * Refactor Land actions hpp=>h/cpp * Refactor LargeScenery actions hpp=>h/cpp * Refactor Load, Maze, Network actions hpp=>h/cpp * Refactor Park actions hpp=>h/cpp * Refactor/style: move private function declarations in actions *.h Previous action .h files included private function declarations with private member variables, before public function declarations. This commit re-orders the header files to the following order: - public member variables - private member variables - public functions - private functions * Refactor Pause action hpp=>h/cpp * Refactor Peep, Place, Player actions hpp=>h/cpp * Refactor Ride actions hpp=>h/cpp * Refactor Scenario, Set*, Sign* actions hpp=>h/cpp * Refactor SmallScenerySetColourAction hpp=>h/cpp * Refactor Staff actions hpp=>h/cpp * Refactor Surface, Tile, Track* actions hpp=>h/cpp * Refactor Wall and Water actions hpp=>h/cpp * Fix various includes and other compile errors Update includes for tests. Move static function declarations to .h files Add explicit includes to various files that were previously implicit (the required header was a nested include in an action hpp file, and the action .h file does not include that header) Move RideSetStatus string enum to the cpp file to avoid unused imports * Xcode: modify project file for actions refactor * Cleanup whitespace and end-of-file newlines Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2020-12-10 07:39:10 +01:00
#include "../windows/Intent.h"
2017-12-14 10:34:12 +01:00
#include "../world/Entrance.h"
2018-01-11 10:59:26 +01:00
#include "../world/Footpath.h"
#include "../world/Scenery.h"
2018-05-01 16:33:16 +02:00
#include "../world/Surface.h"
2022-03-08 01:14:52 +01:00
#include "PatrolArea.h"
2017-10-13 10:06:36 +02:00
#include "Peep.h"
#include <algorithm>
2018-11-21 23:16:04 +01:00
#include <iterator>
using namespace OpenRCT2;
2017-10-13 10:06:36 +02:00
// clang-format off
2022-07-31 14:22:58 +02:00
const StringId StaffCostumeNames[] = {
STR_STAFF_OPTION_COSTUME_PANDA,
STR_STAFF_OPTION_COSTUME_TIGER,
STR_STAFF_OPTION_COSTUME_ELEPHANT,
STR_STAFF_OPTION_COSTUME_ROMAN,
STR_STAFF_OPTION_COSTUME_GORILLA,
STR_STAFF_OPTION_COSTUME_SNOWMAN,
STR_STAFF_OPTION_COSTUME_KNIGHT,
STR_STAFF_OPTION_COSTUME_ASTRONAUT,
STR_STAFF_OPTION_COSTUME_BANDIT,
STR_STAFF_OPTION_COSTUME_SHERIFF,
STR_STAFF_OPTION_COSTUME_PIRATE,
2017-05-28 19:49:32 +02:00
};
2017-10-13 10:06:36 +02:00
// clang-format on
2017-05-28 19:49:32 +02:00
// Maximum manhattan distance that litter can be for a handyman to seek to it
const uint16_t MAX_LITTER_DISTANCE = 3 * COORDS_XY_STEP;
2021-08-25 16:42:09 +02:00
template<> bool EntityBase::Is<Staff>() const
{
return Type == EntityType::Staff;
}
2014-11-02 02:41:00 +01:00
/**
*
* rct2: 0x006C0905
*/
2020-03-07 23:07:47 +01:00
bool Staff::IsLocationInPatrol(const CoordsXY& loc) const
2014-11-02 02:41:00 +01:00
{
// Check if location is in the park
if (!MapIsLocationOwnedOrHasRights(loc))
2018-02-01 17:40:12 +01:00
return false;
2014-11-02 02:41:00 +01:00
// Check if staff has patrol area
if (!HasPatrolArea())
2018-02-01 17:40:12 +01:00
return true;
2014-11-02 02:41:00 +01:00
2020-03-07 23:07:47 +01:00
return IsPatrolAreaSet(loc);
}
// Check whether the location x,y is inside and on the edge of the
// patrol zone for mechanic.
bool Staff::IsLocationOnPatrolEdge(const CoordsXY& loc) const
{
2018-06-22 23:04:19 +02:00
bool onZoneEdge = false;
for (uint8_t neighbourDir = 0; !onZoneEdge && neighbourDir <= 7; neighbourDir++)
2017-10-13 10:06:36 +02:00
{
auto neighbourPos = loc + CoordsDirectionDelta[neighbourDir];
onZoneEdge = !IsLocationInPatrol(neighbourPos);
}
return onZoneEdge;
}
bool Staff::CanIgnoreWideFlag(const CoordsXYZ& staffPos, TileElement* path) const
{
/* Wide flags can potentially wall off parts of a staff patrol zone
* for the heuristic search.
* This function provide doors through such "walls" by defining
* the conditions under which staff can ignore the wide path flag. */
/* Staff can ignore the wide flag on a path on the edge of the patrol
* zone based on its adjacent tiles that are also in the patrol zone
* but not on the patrol zone edge:
* Basic points of interest are:
* - how many such tiles there are;
* - whether there are connected paths on those tiles;
* - whether the connected paths have the wide flag set.
* If there are no such tiles, the path is a concave corner of
* the patrol zone and the wide flag can be ignored.
* If there is one such tile, the path is on a straight side of the
* patrol zone. If this one tile is either a connected wide path or
* not a connected path, the wide flag can be ignored.
* If there are two such tiles, the path is a convex corner of the
* patrol zone. If at most one of these tiles is a connected path or
* both of these tiles are connected wide paths, the wide flag can be
* ignored. */
if (!IsLocationOnPatrolEdge(staffPos))
{
return false;
}
/* Check the connected adjacent paths that are also inside the patrol
* zone but are not on the patrol zone edge have the wide flag set. */
2018-06-22 23:04:19 +02:00
uint8_t total = 0;
uint8_t pathcount = 0;
uint8_t widecount = 0;
for (Direction adjac_dir : ALL_DIRECTIONS)
{
auto adjacPos = staffPos + CoordsXYZ{ CoordsDirectionDelta[adjac_dir].x, CoordsDirectionDelta[adjac_dir].y, 0 };
/* Ignore adjacent tiles outside the patrol zone. */
if (!IsLocationInPatrol(adjacPos))
continue;
/* Ignore adjacent tiles on the patrol zone edge. */
if (IsLocationOnPatrolEdge(adjacPos))
continue;
/* Adjacent tile is inside the patrol zone but not on the
* patrol zone edge. */
total++;
/* Check if path has an edge in adjac_dir */
if (!(path->AsPath()->GetEdges() & (1u << adjac_dir)))
{
continue;
}
2018-09-16 16:17:35 +02:00
if (path->AsPath()->IsSloped())
2017-10-13 10:06:36 +02:00
{
if (path->AsPath()->GetSlopeDirection() == adjac_dir)
2017-10-13 10:06:36 +02:00
{
adjacPos.z += PATH_HEIGHT_STEP;
}
}
/* Search through all adjacent map elements */
TileElement* test_element = MapGetFirstElementAt(adjacPos);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (test_element == nullptr)
return false;
2018-06-22 23:04:19 +02:00
bool pathfound = false;
bool widefound = false;
2017-10-13 10:06:36 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (test_element->GetType() != TileElementType::Path)
{
continue;
}
/* test_element is a path */
if (!PathFinding::IsValidPathZAndDirection(test_element, adjacPos.z / COORDS_Z_STEP, adjac_dir))
2017-10-13 10:06:36 +02:00
continue;
/* test_element is a connected path */
if (!pathfound)
{
pathfound = true;
pathcount++;
}
if (test_element->AsPath()->IsWide())
{
if (!widefound)
{
widefound = true;
widecount++;
}
}
} while (!(test_element++)->IsLastForTile());
}
switch (total)
{
2018-06-22 23:04:19 +02:00
case 0: /* Concave corner */
return true;
2018-06-22 23:04:19 +02:00
case 1: /* Straight side */
case 2: /* Convex corner */
if (pathcount <= total - 1 || widecount == total)
{
return true;
}
}
return false;
}
/**
*
* rct2: 0x006C095B
* returns 0xF if not in a valid patrol area
*/
uint8_t Staff::GetValidPatrolDirections(const CoordsXY& loc) const
2017-10-13 10:06:36 +02:00
{
uint8_t directions = 0;
if (IsLocationInPatrol({ loc.x - COORDS_XY_STEP, loc.y }))
2017-10-13 10:06:36 +02:00
{
directions |= (1 << 0);
}
if (IsLocationInPatrol({ loc.x, loc.y + COORDS_XY_STEP }))
2017-10-13 10:06:36 +02:00
{
directions |= (1 << 1);
}
if (IsLocationInPatrol({ loc.x + COORDS_XY_STEP, loc.y }))
2017-10-13 10:06:36 +02:00
{
directions |= (1 << 2);
}
if (IsLocationInPatrol({ loc.x, loc.y - COORDS_XY_STEP }))
2017-10-13 10:06:36 +02:00
{
directions |= (1 << 3);
}
2017-10-13 10:06:36 +02:00
if (directions == 0)
{
directions = 0xF;
}
return directions;
}
/**
*
* rct2: 0x006C1955
*/
void Staff::ResetStats()
{
for (auto peep : EntityList<Staff>())
2017-10-13 10:06:36 +02:00
{
peep->SetHireDate(GetDate().GetMonthsElapsed());
peep->StaffLawnsMown = 0;
peep->StaffRidesFixed = 0;
peep->StaffGardensWatered = 0;
peep->StaffRidesInspected = 0;
peep->StaffLitterSwept = 0;
peep->StaffVandalsStopped = 0;
peep->StaffBinsEmptied = 0;
}
2015-06-18 15:10:53 +02:00
}
2015-07-13 20:51:46 +02:00
bool Staff::IsPatrolAreaSet(const CoordsXY& coords) const
{
if (PatrolInfo != nullptr)
{
return PatrolInfo->Get(coords);
}
return false;
}
void Staff::SetPatrolArea(const CoordsXY& coords, bool value)
2016-09-10 16:17:18 +02:00
{
if (PatrolInfo == nullptr)
{
if (value)
{
PatrolInfo = new PatrolArea();
}
else
{
return;
}
}
PatrolInfo->Set(coords, value);
2016-09-10 16:17:18 +02:00
}
void Staff::SetPatrolArea(const MapRange& range, bool value)
{
for (int32_t yy = range.GetTop(); yy <= range.GetBottom(); yy += COORDS_XY_STEP)
{
for (int32_t xx = range.GetLeft(); xx <= range.GetRight(); xx += COORDS_XY_STEP)
{
SetPatrolArea({ xx, yy }, value);
}
}
}
void Staff::ClearPatrolArea()
2016-09-10 16:17:18 +02:00
{
delete PatrolInfo;
PatrolInfo = nullptr;
2016-09-10 16:17:18 +02:00
}
bool Staff::HasPatrolArea() const
{
return PatrolInfo == nullptr ? false : !PatrolInfo->IsEmpty();
}
/**
2016-11-13 20:17:49 +01:00
*
* rct2: 0x006BFBE8
2016-11-13 20:17:49 +01:00
*
2020-03-07 23:07:47 +01:00
* Returns INVALID_DIRECTION when no nearby litter or unpathable litter
*/
Direction Staff::HandymanDirectionToNearestLitter() const
2017-10-13 10:06:36 +02:00
{
uint16_t nearestLitterDist = 0xFFFF;
2020-01-19 16:50:01 +01:00
Litter* nearestLitter = nullptr;
for (auto litter : EntityList<Litter>())
2017-10-13 10:06:36 +02:00
{
uint16_t distance = abs(litter->x - x) + abs(litter->y - y) + abs(litter->z - z) * 4;
2017-10-13 10:06:36 +02:00
if (distance < nearestLitterDist)
{
nearestLitterDist = distance;
2018-06-22 23:04:19 +02:00
nearestLitter = litter;
}
}
if (nearestLitterDist > MAX_LITTER_DISTANCE)
2017-10-13 10:06:36 +02:00
{
2020-03-07 23:07:47 +01:00
return INVALID_DIRECTION;
}
auto litterTile = CoordsXY{ nearestLitter->x, nearestLitter->y }.ToTileStart();
if (!IsLocationInPatrol(litterTile))
2017-10-13 10:06:36 +02:00
{
2020-03-07 23:07:47 +01:00
return INVALID_DIRECTION;
}
Direction nextDirection = DirectionFromTo(CoordsXY(x, y), litterTile.ToTileCentre());
CoordsXY nextTile = litterTile.ToTileStart() - CoordsDirectionDelta[nextDirection];
int16_t nextZ = ((z + COORDS_Z_STEP) & 0xFFF0) / COORDS_Z_STEP;
TileElement* tileElement = MapGetFirstElementAt(nextTile);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
2020-03-07 23:07:47 +01:00
return INVALID_DIRECTION;
2017-10-13 10:06:36 +02:00
do
{
if (tileElement->BaseHeight != nextZ)
continue;
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() == TileElementType::Entrance || tileElement->GetType() == TileElementType::Track)
2017-10-13 10:06:36 +02:00
{
2020-03-07 23:07:47 +01:00
return INVALID_DIRECTION;
}
} while (!(tileElement++)->IsLastForTile());
nextTile = CoordsXY(x, y).ToTileStart() + CoordsDirectionDelta[nextDirection];
tileElement = MapGetFirstElementAt(nextTile);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tileElement == nullptr)
2020-03-07 23:07:47 +01:00
return INVALID_DIRECTION;
2017-10-13 10:06:36 +02:00
do
{
if (tileElement->BaseHeight != nextZ)
continue;
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() == TileElementType::Entrance || tileElement->GetType() == TileElementType::Track)
2017-10-13 10:06:36 +02:00
{
2020-03-07 23:07:47 +01:00
return INVALID_DIRECTION;
}
} while (!(tileElement++)->IsLastForTile());
return nextDirection;
}
/**
2016-02-03 19:17:30 +01:00
*
* rct2: 0x006BF931
*/
uint8_t Staff::HandymanDirectionToUncutGrass(uint8_t valid_directions) const
2017-10-13 10:06:36 +02:00
{
if (!(GetNextIsSurface()))
2017-10-13 10:06:36 +02:00
{
auto surfaceElement = MapGetSurfaceElementAt(NextLoc);
if (surfaceElement == nullptr)
return INVALID_DIRECTION;
2016-02-03 19:17:30 +01:00
if (NextLoc.z != surfaceElement->GetBaseZ())
return INVALID_DIRECTION;
2016-02-03 19:17:30 +01:00
if (GetNextIsSloped())
2017-10-13 10:06:36 +02:00
{
if (surfaceElement->GetSlope() != PathSlopeToLandSlope[GetNextDirection()])
return INVALID_DIRECTION;
}
else if (surfaceElement->GetSlope() != TILE_ELEMENT_SLOPE_FLAT)
return INVALID_DIRECTION;
}
2016-02-03 19:17:30 +01:00
uint8_t chosenDirection = ScenarioRand() & 0x3;
for (uint8_t i = 0; i < 4; ++i, ++chosenDirection)
2017-10-13 10:06:36 +02:00
{
chosenDirection &= 0x3;
2016-02-03 19:17:30 +01:00
2017-10-13 10:06:36 +02:00
if (!(valid_directions & (1 << chosenDirection)))
{
continue;
}
2016-02-03 19:17:30 +01:00
CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[chosenDirection];
2016-02-03 19:17:30 +01:00
if (!MapIsLocationValid(chosenTile))
continue;
2016-02-03 19:17:30 +01:00
auto surfaceElement = MapGetSurfaceElementAt(chosenTile);
if (surfaceElement != nullptr)
{
if (std::abs(surfaceElement->GetBaseZ() - NextLoc.z) <= 2 * COORDS_Z_STEP)
{
if (surfaceElement->CanGrassGrow() && (surfaceElement->GetGrassLength() & 0x7) >= GRASS_LENGTH_CLEAR_1)
{
return chosenDirection;
}
}
}
}
return INVALID_DIRECTION;
2016-02-03 19:17:30 +01:00
}
/**
*
* rct2: 0x006BFD9C
*/
Direction Staff::HandymanDirectionRandSurface(uint8_t validDirections) const
2017-10-13 10:06:36 +02:00
{
Direction newDirection = ScenarioRand() % NumOrthogonalDirections;
2020-03-07 23:07:47 +01:00
for (int32_t i = 0; i < NumOrthogonalDirections; ++i, ++newDirection)
2017-10-13 10:06:36 +02:00
{
2020-03-07 23:07:47 +01:00
newDirection %= NumOrthogonalDirections;
if (!(validDirections & (1 << newDirection)))
continue;
2020-03-07 23:07:47 +01:00
CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
if (MapSurfaceIsBlocked(chosenTile))
continue;
break;
}
// If it tries all directions this is required
// to make it back to the first direction and
// override validDirections
2020-03-07 23:07:47 +01:00
newDirection %= NumOrthogonalDirections;
return newDirection;
}
2016-02-03 19:17:30 +01:00
/**
*
* rct2: 0x006BFBA8
*/
2020-03-07 23:07:47 +01:00
bool Staff::DoHandymanPathFinding()
{
StaffMowingTimeout++;
Direction litterDirection = INVALID_DIRECTION;
uint8_t validDirections = GetValidPatrolDirections(NextLoc);
if ((StaffOrders & STAFF_ORDERS_SWEEPING) && ((GetGameState().CurrentTicks + Id.ToUnderlying()) & 0xFFF) > 110)
2017-10-13 10:06:36 +02:00
{
litterDirection = HandymanDirectionToNearestLitter();
}
2020-03-07 23:07:47 +01:00
Direction newDirection = INVALID_DIRECTION;
if (litterDirection == INVALID_DIRECTION && (StaffOrders & STAFF_ORDERS_MOWING) && StaffMowingTimeout >= 12)
2017-10-13 10:06:36 +02:00
{
newDirection = HandymanDirectionToUncutGrass(validDirections);
}
2020-03-07 23:07:47 +01:00
if (newDirection == INVALID_DIRECTION)
2017-10-13 10:06:36 +02:00
{
2020-03-07 23:07:47 +01:00
if (GetNextIsSurface())
2017-10-13 10:06:36 +02:00
{
2020-03-07 23:07:47 +01:00
newDirection = HandymanDirectionRandSurface(validDirections);
2017-10-13 10:06:36 +02:00
}
else
{
auto* pathElement = MapGetPathElementAt(TileCoordsXYZ{ NextLoc });
if (pathElement == nullptr)
2018-02-01 17:40:12 +01:00
return true;
uint8_t pathDirections = (pathElement->GetEdges() & validDirections) & 0xF;
2017-10-13 10:06:36 +02:00
if (pathDirections == 0)
{
2020-03-07 23:07:47 +01:00
newDirection = HandymanDirectionRandSurface(validDirections);
2017-10-13 10:06:36 +02:00
}
else
{
bool chooseRandom = true;
2020-03-05 18:27:36 +01:00
if (litterDirection != INVALID_DIRECTION && pathDirections & (1 << litterDirection))
2017-10-13 10:06:36 +02:00
{
/// Check whether path is a queue path and connected to a ride
bool connectedQueue = (pathElement->IsQueue() && !pathElement->GetRideIndex().IsNull());
/// When in a queue path make the probability of following litter much lower (10% instead of 90%)
/// as handymen often get stuck when there is litter on a normal path next to a queue they are in
uint32_t chooseRandomProbability = connectedQueue ? 0xE666 : 0x1999;
if ((ScenarioRand() & 0xFFFF) >= chooseRandomProbability)
2017-10-13 10:06:36 +02:00
{
chooseRandom = false;
2020-03-07 23:07:47 +01:00
newDirection = litterDirection;
}
2017-10-13 10:06:36 +02:00
}
else
{
pathDirections &= ~(1 << DirectionReverse(PeepDirection));
2017-10-13 10:06:36 +02:00
if (pathDirections == 0)
{
pathDirections |= 1 << DirectionReverse(PeepDirection);
}
}
if (chooseRandom)
2017-10-13 10:06:36 +02:00
{
do
{
newDirection = ScenarioRand() & 3;
2020-03-07 23:07:47 +01:00
} while ((pathDirections & (1 << newDirection)) == 0);
}
}
}
}
// newDirection can only contain a cardinal direction at this point, no diagonals
assert(DirectionValid(newDirection));
2020-03-07 23:07:47 +01:00
CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
while (!MapIsLocationValid(chosenTile))
2017-10-13 10:06:36 +02:00
{
2020-03-07 23:07:47 +01:00
newDirection = HandymanDirectionRandSurface(validDirections);
chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
}
PeepDirection = newDirection;
SetDestination(chosenTile + CoordsXY{ 16, 16 }, 3);
if (State == PeepState::Queuing)
2017-10-13 10:06:36 +02:00
{
DestinationTolerance = (ScenarioRand() & 7) + 2;
}
2018-02-01 17:40:12 +01:00
return false;
}
Direction Staff::DirectionSurface(Direction initialDirection) const
2017-10-13 10:06:36 +02:00
{
uint8_t direction = initialDirection;
for (int32_t i = 0; i < 3; ++i)
2017-10-13 10:06:36 +02:00
{
// Looks left and right from initial direction
2017-10-13 10:06:36 +02:00
switch (i)
{
2018-06-22 23:04:19 +02:00
case 1:
direction++;
if (ScenarioRand() & 1)
2018-06-22 23:04:19 +02:00
{
direction -= 2;
}
break;
case 2:
direction -= 2;
2018-06-22 23:04:19 +02:00
break;
}
direction &= 3;
2022-10-04 08:51:27 +02:00
if (WallInTheWay({ NextLoc, NextLoc.z, NextLoc.z + PEEP_CLEARANCE_HEIGHT }, direction))
continue;
if (WallInTheWay({ NextLoc, NextLoc.z, NextLoc.z + PEEP_CLEARANCE_HEIGHT }, DirectionReverse(direction)))
continue;
CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[direction];
if (!MapSurfaceIsBlocked(chosenTile))
2017-10-13 10:06:36 +02:00
{
return direction;
}
}
return initialDirection;
}
2016-02-09 18:53:24 +01:00
/**
*
* rct2: 0x006BFF45
*/
Direction Staff::MechanicDirectionSurface() const
2017-10-13 10:06:36 +02:00
{
Direction direction = ScenarioRand() & 3;
2016-02-09 18:53:24 +01:00
auto ride = GetRide(CurrentRide);
if (ride != nullptr && (State == PeepState::Answering || State == PeepState::HeadingToInspection) && (ScenarioRand() & 1))
2017-10-13 10:06:36 +02:00
{
auto location = ride->GetStation(CurrentRideStation).Exit;
if (location.IsNull())
2017-10-13 10:06:36 +02:00
{
location = ride->GetStation(CurrentRideStation).Entrance;
}
2016-02-09 18:53:24 +01:00
direction = DirectionFromTo(CoordsXY(x, y), location.ToCoordsXY());
}
2016-02-09 18:53:24 +01:00
return DirectionSurface(direction);
2016-02-09 18:53:24 +01:00
}
/**
*
2016-02-06 11:34:40 +01:00
* rct2: 0x006C02D1
*/
Direction Staff::MechanicDirectionPathRand(uint8_t pathDirections) const
2017-10-13 10:06:36 +02:00
{
if (ScenarioRand() & 1)
2017-10-13 10:06:36 +02:00
{
if (pathDirections & (1 << PeepDirection))
return PeepDirection;
}
// Modified from original to spam scenario_rand less
uint8_t direction = ScenarioRand() & 3;
for (int32_t i = 0; i < 4; ++i, ++direction)
2017-10-13 10:06:36 +02:00
{
direction &= 3;
if (pathDirections & (1 << direction))
return direction;
}
// This will never happen as pathDirections always has a bit set.
return PeepDirection;
2016-02-06 11:34:40 +01:00
}
/**
*
* rct2: 0x006C0121
*/
Direction Staff::MechanicDirectionPath(uint8_t validDirections, PathElement* pathElement)
2017-10-13 10:06:36 +02:00
{
uint8_t pathDirections = pathElement->GetEdges();
pathDirections &= validDirections;
2017-10-13 10:06:36 +02:00
if (pathDirections == 0)
{
return MechanicDirectionSurface();
}
// Check if this is dead end - i.e. only way out is the reverse direction.
pathDirections &= ~(1 << DirectionReverse(PeepDirection));
2017-10-13 10:06:36 +02:00
if (pathDirections == 0)
{
pathDirections |= (1 << DirectionReverse(PeepDirection));
}
Direction direction = UtilBitScanForward(pathDirections);
pathDirections &= ~(1 << direction);
2017-10-13 10:06:36 +02:00
if (pathDirections == 0)
{
if (State != PeepState::Answering && State != PeepState::HeadingToInspection)
2017-10-13 10:06:36 +02:00
{
return direction;
}
if (SubState != 2)
2017-10-13 10:06:36 +02:00
{
return direction;
}
SubState = 3;
}
pathDirections |= (1 << direction);
// Mechanic is heading to ride (either broken down or for inspection).
auto ride = GetRide(CurrentRide);
if (ride != nullptr && (State == PeepState::Answering || State == PeepState::HeadingToInspection))
2017-10-13 10:06:36 +02:00
{
/* Find location of the exit for the target ride station
* or if the ride has no exit, the entrance. */
TileCoordsXYZD location = ride->GetStation(CurrentRideStation).Exit;
if (location.IsNull())
2017-10-13 10:06:36 +02:00
{
location = ride->GetStation(CurrentRideStation).Entrance;
// If no entrance is present either. This is an incorrect state.
if (location.IsNull())
{
return MechanicDirectionPathRand(pathDirections);
}
}
gPeepPathFindIgnoreForeignQueues = false;
gPeepPathFindQueueRideIndex = RideId::GetNull();
const auto goalPos = TileCoordsXYZ{ location };
Direction pathfindDirection = PathFinding::ChooseDirection(TileCoordsXYZ{ NextLoc }, goalPos, *this);
if (pathfindDirection == INVALID_DIRECTION)
2017-10-13 10:06:36 +02:00
{
/* Heuristic search failed for all directions.
* Reset the PathfindGoal - this means that the PathfindHistory
* will be reset in the next call to GuestPathfinding::ChooseDirection().
* This lets the heuristic search "try again" in case the player has
* edited the path layout or the mechanic was already stuck in the
* save game (e.g. with a worse version of the pathfinding). */
ResetPathfindGoal();
return MechanicDirectionPathRand(pathDirections);
}
return pathfindDirection;
}
return MechanicDirectionPathRand(pathDirections);
2016-02-06 11:34:40 +01:00
}
2016-02-06 11:34:40 +01:00
/**
*
* rct2: 0x006BFF2C
*/
2020-03-07 23:07:47 +01:00
bool Staff::DoMechanicPathFinding()
2017-10-13 10:06:36 +02:00
{
uint8_t validDirections = GetValidPatrolDirections(NextLoc);
2020-03-07 23:07:47 +01:00
Direction newDirection = INVALID_DIRECTION;
if (GetNextIsSurface())
2017-10-13 10:06:36 +02:00
{
newDirection = MechanicDirectionSurface();
}
2017-10-13 10:06:36 +02:00
else
{
auto* pathElement = MapGetPathElementAt(TileCoordsXYZ{ NextLoc });
2018-01-04 06:58:44 +01:00
if (pathElement == nullptr)
2018-02-01 17:40:12 +01:00
return true;
newDirection = MechanicDirectionPath(validDirections, pathElement);
}
// countof(CoordsDirectionDelta)
assert(DirectionValid(newDirection));
2020-03-07 23:07:47 +01:00
CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
while (!MapIsLocationValid(chosenTile))
2017-10-13 10:06:36 +02:00
{
newDirection = MechanicDirectionSurface();
2020-03-07 23:07:47 +01:00
chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
}
PeepDirection = newDirection;
auto tolerance = (ScenarioRand() & 7) + 2;
SetDestination(chosenTile + CoordsXY{ 16, 16 }, tolerance);
2018-02-01 17:40:12 +01:00
return false;
}
2016-02-09 18:53:24 +01:00
/**
2017-10-13 10:06:36 +02:00
*
* rct2: 0x006C050B
*/
Direction Staff::DirectionPath(uint8_t validDirections, PathElement* pathElement) const
2017-10-13 10:06:36 +02:00
{
uint8_t pathDirections = pathElement->GetEdges();
if (State != PeepState::Answering && State != PeepState::HeadingToInspection)
2017-10-13 10:06:36 +02:00
{
pathDirections &= validDirections;
}
2017-10-13 10:06:36 +02:00
if (pathDirections == 0)
{
return DirectionSurface(ScenarioRand() & 3);
}
pathDirections &= ~(1 << DirectionReverse(PeepDirection));
2017-10-13 10:06:36 +02:00
if (pathDirections == 0)
{
pathDirections |= (1 << DirectionReverse(PeepDirection));
}
Direction direction = UtilBitScanForward(pathDirections);
// If this is the only direction they can go, then go
if (pathDirections == (1 << direction))
2017-10-13 10:06:36 +02:00
{
return direction;
}
direction = ScenarioRand() & 3;
for (uint8_t i = 0; i < NumOrthogonalDirections; ++i, direction = DirectionNext(direction))
2017-10-13 10:06:36 +02:00
{
if (pathDirections & (1 << direction))
return direction;
}
// This will never happen as pathDirections will always have a bit set
return direction;
2016-02-09 18:53:24 +01:00
}
/**
*
* rct2: 0x006C0351
*/
2020-03-07 23:07:47 +01:00
bool Staff::DoMiscPathFinding()
2017-10-13 10:06:36 +02:00
{
uint8_t validDirections = GetValidPatrolDirections(NextLoc);
2020-03-07 23:07:47 +01:00
Direction newDirection = INVALID_DIRECTION;
if (GetNextIsSurface())
2017-10-13 10:06:36 +02:00
{
newDirection = DirectionSurface(ScenarioRand() & 3);
}
2017-10-13 10:06:36 +02:00
else
{
auto* pathElement = MapGetPathElementAt(TileCoordsXYZ{ NextLoc });
2018-01-04 06:58:44 +01:00
if (pathElement == nullptr)
2018-02-01 17:40:12 +01:00
return true;
newDirection = DirectionPath(validDirections, pathElement);
}
2020-03-07 23:07:47 +01:00
CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
while (!MapIsLocationValid(chosenTile))
2017-10-13 10:06:36 +02:00
{
newDirection = DirectionSurface(ScenarioRand() & 3);
2020-03-07 23:07:47 +01:00
chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
}
PeepDirection = newDirection;
auto tolerance = (ScenarioRand() & 7) + 2;
SetDestination(chosenTile + CoordsXY{ 16, 16 }, tolerance);
2018-02-01 17:40:12 +01:00
return false;
2016-02-09 18:53:24 +01:00
}
bool Staff::IsMechanicHeadingToFixRideBlockingPath()
{
if (!IsMechanic())
return false;
auto tileCoords = TileCoordsXYZ(CoordsXYZ{ GetDestination(), NextLoc.z });
auto trackElement = MapGetFirstTileElementWithBaseHeightBetween<TrackElement>(
{ tileCoords, tileCoords.z + PATH_HEIGHT_STEP });
if (trackElement == nullptr)
return false;
auto ride = GetRide(trackElement->GetRideIndex());
if (ride == nullptr)
return false;
return ride->id == CurrentRide;
}
2016-02-09 19:17:37 +01:00
/**
*
* rct2: 0x006C086D
*/
void Staff::EntertainerUpdateNearbyPeeps() const
2017-10-13 10:06:36 +02:00
{
for (auto guest : EntityList<Guest>())
2017-10-13 10:06:36 +02:00
{
if (guest->x == LOCATION_NULL)
continue;
int16_t z_dist = abs(z - guest->z);
if (z_dist > 48)
continue;
int16_t x_dist = abs(x - guest->x);
int16_t y_dist = abs(y - guest->y);
if (x_dist > 96)
continue;
if (y_dist > 96)
continue;
if (guest->State == PeepState::Walking)
2017-10-13 10:06:36 +02:00
{
2024-04-15 16:48:41 +02:00
guest->HappinessTarget = std::min(guest->HappinessTarget + 4, kPeepMaxHappiness);
}
else if (guest->State == PeepState::Queuing)
2017-10-13 10:06:36 +02:00
{
guest->TimeInQueue = std::max(0, guest->TimeInQueue - 200);
2024-04-15 16:48:41 +02:00
guest->HappinessTarget = std::min(guest->HappinessTarget + 3, kPeepMaxHappiness);
}
}
2016-02-09 19:17:37 +01:00
}
/**
*
* rct2: 0x006C05AE
*/
2020-03-07 23:07:47 +01:00
bool Staff::DoEntertainerPathFinding()
2017-10-13 10:06:36 +02:00
{
if (((ScenarioRand() & 0xFFFF) <= 0x4000) && IsActionInterruptable())
2017-10-13 10:06:36 +02:00
{
Action = (ScenarioRand() & 1) ? PeepActionType::Wave2 : PeepActionType::Joy;
2020-06-08 01:18:42 +02:00
ActionFrame = 0;
ActionSpriteImageOffset = 0;
2016-02-09 19:17:37 +01:00
2020-03-07 23:07:47 +01:00
UpdateCurrentActionSpriteType();
EntertainerUpdateNearbyPeeps();
}
2016-02-09 19:17:37 +01:00
2020-03-07 23:07:47 +01:00
return DoMiscPathFinding();
2016-02-09 19:17:37 +01:00
}
/**
2016-02-03 19:17:30 +01:00
*
* rct2: 0x006BF926
*/
2020-03-07 23:07:47 +01:00
bool Staff::DoPathFinding()
2017-10-13 10:06:36 +02:00
{
2020-08-17 00:20:00 +02:00
switch (AssignedStaffType)
2017-10-13 10:06:36 +02:00
{
case StaffType::Handyman:
2020-03-07 23:07:47 +01:00
return DoHandymanPathFinding();
case StaffType::Mechanic:
2020-03-07 23:07:47 +01:00
return DoMechanicPathFinding();
case StaffType::Security:
2020-03-07 23:07:47 +01:00
return DoMiscPathFinding();
case StaffType::Entertainer:
2020-03-07 23:07:47 +01:00
return DoEntertainerPathFinding();
2018-06-22 23:04:19 +02:00
default:
assert(false);
return 0;
}
}
uint8_t Staff::GetCostume() const
{
return EnumValue(SpriteType) - EnumValue(PeepSpriteType::EntertainerPanda);
}
void Staff::SetCostume(uint8_t value)
{
auto costume = static_cast<EntertainerCostume>(value);
SpriteType = EntertainerCostumeToSprite(costume);
UpdateAction();
}
void Staff::SetHireDate(int32_t hireDate)
{
HireDate = hireDate;
}
int32_t Staff::GetHireDate() const
{
return HireDate;
}
PeepSpriteType EntertainerCostumeToSprite(EntertainerCostume entertainerType)
{
uint8_t value = static_cast<uint8_t>(entertainerType);
PeepSpriteType newSpriteType = static_cast<PeepSpriteType>(value + EnumValue(PeepSpriteType::EntertainerPanda));
return newSpriteType;
}
colour_t StaffGetColour(StaffType staffType)
2016-09-10 16:17:18 +02:00
{
const auto& gameState = GetGameState();
2017-10-13 10:06:36 +02:00
switch (staffType)
{
case StaffType::Handyman:
return gameState.StaffHandymanColour;
case StaffType::Mechanic:
return gameState.StaffMechanicColour;
case StaffType::Security:
return gameState.StaffSecurityColour;
case StaffType::Entertainer:
2018-06-22 23:04:19 +02:00
return 0;
default:
assert(false);
return 0;
}
2016-09-10 16:17:18 +02:00
}
GameActions::Result StaffSetColour(StaffType staffType, colour_t value)
2016-09-10 16:17:18 +02:00
{
auto& gameState = GetGameState();
2017-10-13 10:06:36 +02:00
switch (staffType)
{
case StaffType::Handyman:
gameState.StaffHandymanColour = value;
2018-06-22 23:04:19 +02:00
break;
case StaffType::Mechanic:
gameState.StaffMechanicColour = value;
2018-06-22 23:04:19 +02:00
break;
case StaffType::Security:
gameState.StaffSecurityColour = value;
2018-06-22 23:04:19 +02:00
break;
default:
return GameActions::Result(
GameActions::Status::InvalidParameters, STR_ERR_INVALID_PARAMETER, STR_ERR_ACTION_INVALID_FOR_THAT_STAFF_TYPE);
}
return GameActions::Result();
2016-09-10 16:17:18 +02:00
}
2017-01-24 14:05:02 +01:00
uint32_t StaffGetAvailableEntertainerCostumes()
2017-01-24 14:05:02 +01:00
{
uint32_t entertainerCostumes = 0;
for (int32_t i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++)
2017-10-13 10:06:36 +02:00
{
if (SceneryGroupIsInvented(i))
2017-10-13 10:06:36 +02:00
{
const auto sgEntry = OpenRCT2::ObjectManager::GetObjectEntry<SceneryGroupEntry>(i);
2017-11-19 21:39:13 +01:00
entertainerCostumes |= sgEntry->entertainer_costumes;
}
}
2017-01-24 14:05:02 +01:00
// For some reason the flags are +4 from the actual costume IDs
entertainerCostumes >>= 4;
2017-01-24 14:05:02 +01:00
// Fix #6593: force enable the default costumes, which normally get enabled through the default scenery groups.
entertainerCostumes |= (1 << static_cast<uint8_t>(EntertainerCostume::Panda))
| (1 << static_cast<uint8_t>(EntertainerCostume::Tiger)) | (1 << static_cast<uint8_t>(EntertainerCostume::Elephant));
return entertainerCostumes;
2017-01-24 14:05:02 +01:00
}
int32_t StaffGetAvailableEntertainerCostumeList(EntertainerCostume* costumeList)
2017-01-24 14:05:02 +01:00
{
uint32_t availableCostumes = StaffGetAvailableEntertainerCostumes();
2018-06-22 23:04:19 +02:00
int32_t numCostumes = 0;
for (uint8_t i = 0; i < static_cast<uint8_t>(EntertainerCostume::Count); i++)
2017-10-13 10:06:36 +02:00
{
if (availableCostumes & (1 << i))
{
costumeList[numCostumes++] = static_cast<EntertainerCostume>(i);
}
}
return numCostumes;
2017-01-24 14:05:02 +01:00
}
2018-04-02 19:00:36 +02:00
/** rct2: 0x009929C8 */
static constexpr CoordsXY _MowingWaypoints[] = {
2018-04-02 19:00:36 +02:00
{ 28, 28 }, { 28, 4 }, { 20, 4 }, { 20, 28 }, { 12, 28 }, { 12, 4 }, { 4, 4 }, { 4, 28 },
};
/**
*
* rct2: 0x006BF567
*/
2019-02-28 20:28:58 +01:00
void Staff::UpdateMowing()
2018-04-02 19:00:36 +02:00
{
if (!CheckForPath())
return;
while (true)
{
2021-09-13 18:47:13 +02:00
if (auto loc = UpdateAction(); loc.has_value())
2018-04-02 19:00:36 +02:00
{
int16_t checkZ = TileElementHeight(*loc);
2021-09-13 18:47:13 +02:00
MoveTo({ loc.value(), checkZ });
2018-04-02 19:00:36 +02:00
return;
}
2020-06-09 04:47:45 +02:00
Var37++;
2018-04-02 19:00:36 +02:00
2020-06-09 04:47:45 +02:00
if (Var37 == 1)
2018-04-02 19:00:36 +02:00
{
SwitchToSpecialSprite(2);
}
2020-06-09 04:47:45 +02:00
if (Var37 == std::size(_MowingWaypoints))
2018-04-02 19:00:36 +02:00
{
2018-04-02 19:18:15 +02:00
StateReset();
2018-04-02 19:00:36 +02:00
return;
}
auto destination = _MowingWaypoints[Var37] + NextLoc;
SetDestination(destination);
2018-04-02 19:00:36 +02:00
2020-06-09 04:47:45 +02:00
if (Var37 != 7)
2018-04-02 19:00:36 +02:00
continue;
auto surfaceElement = MapGetSurfaceElementAt(NextLoc);
if (surfaceElement != nullptr && surfaceElement->CanGrassGrow())
2018-04-02 19:00:36 +02:00
{
surfaceElement->SetGrassLength(GRASS_LENGTH_MOWED);
MapInvalidateTileZoom0({ NextLoc, surfaceElement->GetBaseZ(), surfaceElement->GetBaseZ() + 16 });
2018-04-02 19:00:36 +02:00
}
StaffLawnsMown++;
WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
2018-04-02 19:00:36 +02:00
}
}
/**
*
* rct2: 0x006BF7E6
*/
2019-02-28 20:28:58 +01:00
void Staff::UpdateWatering()
2018-04-02 19:00:36 +02:00
{
StaffMowingTimeout = 0;
if (SubState == 0)
2018-04-02 19:00:36 +02:00
{
if (!CheckForPath())
return;
uint8_t pathingResult;
2018-04-02 19:00:36 +02:00
PerformNextAction(pathingResult);
if (!(pathingResult & PATHING_DESTINATION_REACHED))
return;
Orientation = (Var37 & 3) << 3;
Action = PeepActionType::StaffWatering;
2020-06-08 01:18:42 +02:00
ActionFrame = 0;
ActionSpriteImageOffset = 0;
2018-04-02 19:00:36 +02:00
UpdateCurrentActionSpriteType();
SubState = 1;
2018-04-02 19:00:36 +02:00
}
else if (SubState == 1)
2018-04-02 19:00:36 +02:00
{
if (!IsActionWalking())
2018-04-02 19:00:36 +02:00
{
UpdateAction();
2019-08-21 12:31:05 +02:00
Invalidate();
2018-04-02 19:00:36 +02:00
return;
}
2020-06-09 04:47:45 +02:00
auto actionLoc = CoordsXY{ NextLoc } + CoordsDirectionDelta[Var37];
2018-04-02 19:00:36 +02:00
TileElement* tile_element = MapGetFirstElementAt(actionLoc);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tile_element == nullptr)
return;
2018-04-02 19:00:36 +02:00
do
{
2021-12-11 00:39:39 +01:00
if (tile_element->GetType() != TileElementType::SmallScenery)
2018-04-02 19:00:36 +02:00
continue;
if (abs(NextLoc.z - tile_element->GetBaseZ()) > 4 * COORDS_Z_STEP)
2018-04-02 19:00:36 +02:00
continue;
const auto* sceneryEntry = tile_element->AsSmallScenery()->GetEntry();
2018-04-02 19:00:36 +02:00
if (sceneryEntry == nullptr || !sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_CAN_BE_WATERED))
2018-04-02 19:00:36 +02:00
continue;
tile_element->AsSmallScenery()->SetAge(0);
MapInvalidateTileZoom0({ actionLoc, tile_element->GetBaseZ(), tile_element->GetClearanceZ() });
StaffGardensWatered++;
WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
} while (!(tile_element++)->IsLastForTile());
2018-04-02 19:00:36 +02:00
2018-04-02 19:18:15 +02:00
StateReset();
2018-04-02 19:00:36 +02:00
}
}
/**
*
* rct2: 0x006BF6C9
*/
2019-02-28 20:28:58 +01:00
void Staff::UpdateEmptyingBin()
2018-04-02 19:00:36 +02:00
{
StaffMowingTimeout = 0;
2018-04-02 19:00:36 +02:00
if (SubState == 0)
2018-04-02 19:00:36 +02:00
{
if (!CheckForPath())
return;
uint8_t pathingResult;
2018-04-02 19:00:36 +02:00
PerformNextAction(pathingResult);
if (!(pathingResult & PATHING_DESTINATION_REACHED))
return;
Orientation = (Var37 & 3) << 3;
Action = PeepActionType::StaffEmptyBin;
2020-06-08 01:18:42 +02:00
ActionFrame = 0;
ActionSpriteImageOffset = 0;
2018-04-02 19:00:36 +02:00
UpdateCurrentActionSpriteType();
SubState = 1;
2018-04-02 19:00:36 +02:00
}
else if (SubState == 1)
2018-04-02 19:00:36 +02:00
{
if (IsActionWalking())
2018-04-02 19:00:36 +02:00
{
2018-04-02 19:18:15 +02:00
StateReset();
2018-04-02 19:00:36 +02:00
return;
}
UpdateAction();
2019-08-21 12:31:05 +02:00
Invalidate();
2018-04-02 19:00:36 +02:00
2020-06-08 01:18:42 +02:00
if (ActionFrame != 11)
2018-04-02 19:00:36 +02:00
return;
TileElement* tile_element = MapGetFirstElementAt(NextLoc);
Avoid dereferencing map_get_first_element_at nullptr on libopenrct2 (#10013) * Avoid dereferencing map_get_first_element_at nullptr on Map.cpp * Avoid dereferencing map_get_first_element_at nullptr on MapAnimation.cpp Returning true or internal control variable, based on what was seen on `map_animation_invalidate_track_onridephoto` * Avoid dereferencing map_get_first_element_at nullptr on Park.cpp * Avoid dereferencing map_get_first_element_at nullptr on Scenery.cpp * Avoid dereferencing map_get_first_element_at nullptr on Sprite.cpp * Avoid dereferencing map_get_first_element_at nullptr on TileInspector.cpp * Avoid dereferencing map_get_first_element_at nullptr on Wall.cpp * Avoid dereferencing map_get_first_element_at nullptr on Fountain.cpp * Avoid dereferencing map_get_first_element_at nullptr on Footpath.cpp * Avoid dereferencing map_get_first_element_at nullptr on Entrance.cpp * Avoid dereferencing map_get_first_element_at nullptr on Banner.cpp * Avoid dereferencing map_get_first_element_at nullptr on Vehicle.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesignSave.cpp * Avoid dereferencing map_get_first_element_at nullptr on TrackDesign.cpp * Avoid dereferencing map_get_first_element_at nullptr on Track.cpp * Avoid dereferencing map_get_first_element_at nullptr on Station.cpp * Avoid dereferencing map_get_first_element_at nullptr on RideRatings.cpp * Avoid dereferencing map_get_first_element_at nullptr on Ride.cpp * Avoid dereferencing map_get_first_element_at nullptr on S4Importer.cpp * Avoid dereferencing map_get_first_element_at nullptr on Staff.cpp * Avoid dereferencing map_get_first_element_at nullptr on Peep.cpp * Avoid dereferencing map_get_first_element_at nullptr on GuestPathfinding.cpp * Avoid dereferencing map_get_first_element_at nullptr on Guest.cpp * Avoid dereferencing map_get_first_element_at nullptr on VirtualFloor.cpp * Avoid dereferencing map_get_first_element_at nullptr on Paint.TileElement.cpp * Fix issues raised on review * Fix remaining review issues. * Early exit on loops if tileElement is nullptr * Fix clang-format issues
2019-10-09 16:02:21 +02:00
if (tile_element == nullptr)
return;
2018-04-02 19:00:36 +02:00
for (;; tile_element++)
{
2021-12-11 00:39:39 +01:00
if (tile_element->GetType() == TileElementType::Path)
2018-04-02 19:00:36 +02:00
{
if (NextLoc.z == tile_element->GetBaseZ())
2018-04-02 19:00:36 +02:00
break;
}
if ((tile_element)->IsLastForTile())
2018-04-02 19:00:36 +02:00
{
2018-04-02 19:18:15 +02:00
StateReset();
2018-04-02 19:00:36 +02:00
return;
}
}
if (!tile_element->AsPath()->HasAddition())
2018-04-02 19:00:36 +02:00
{
2018-04-02 19:18:15 +02:00
StateReset();
2018-04-02 19:00:36 +02:00
return;
}
auto* pathAddEntry = tile_element->AsPath()->GetAdditionEntry();
if (!(pathAddEntry->flags & PATH_ADDITION_FLAG_IS_BIN) || tile_element->AsPath()->IsBroken()
|| tile_element->AsPath()->AdditionIsGhost())
2018-04-02 19:00:36 +02:00
{
2018-04-02 19:18:15 +02:00
StateReset();
2018-04-02 19:00:36 +02:00
return;
}
2020-06-09 04:47:45 +02:00
uint8_t additionStatus = tile_element->AsPath()->GetAdditionStatus() | ((3 << Var37) << Var37);
tile_element->AsPath()->SetAdditionStatus(additionStatus);
2018-04-02 19:00:36 +02:00
MapInvalidateTileZoom0({ NextLoc, tile_element->GetBaseZ(), tile_element->GetClearanceZ() });
StaffBinsEmptied++;
WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
2018-04-02 19:00:36 +02:00
}
}
/**
*
* rct2: 0x6BF641
*/
2019-02-28 20:28:58 +01:00
void Staff::UpdateSweeping()
2018-04-02 19:00:36 +02:00
{
StaffMowingTimeout = 0;
2018-04-02 19:00:36 +02:00
if (!CheckForPath())
return;
if (Action == PeepActionType::StaffSweep && ActionFrame == 8)
2018-04-02 19:00:36 +02:00
{
// Remove sick at this location
2021-09-28 02:16:04 +02:00
Litter::RemoveAt(GetLocation());
StaffLitterSwept++;
WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
2018-04-02 19:00:36 +02:00
}
2021-09-13 18:47:13 +02:00
if (auto loc = UpdateAction(); loc.has_value())
2018-04-02 19:00:36 +02:00
{
int16_t actionZ = GetZOnSlope((*loc).x, (*loc).y);
2021-09-13 18:47:13 +02:00
MoveTo({ loc.value(), actionZ });
2018-04-02 19:00:36 +02:00
return;
}
2020-06-09 04:47:45 +02:00
Var37++;
if (Var37 != 2)
2018-04-02 19:00:36 +02:00
{
Action = PeepActionType::StaffSweep;
2020-06-08 01:18:42 +02:00
ActionFrame = 0;
ActionSpriteImageOffset = 0;
2018-04-02 19:00:36 +02:00
UpdateCurrentActionSpriteType();
return;
}
2018-04-02 19:18:15 +02:00
StateReset();
2018-04-02 19:00:36 +02:00
}
/**
*
* rct2: 0x006C16D7
*/
2019-02-28 20:28:58 +01:00
void Staff::UpdateHeadingToInspect()
2018-04-02 19:00:36 +02:00
{
auto ride = GetRide(CurrentRide);
if (ride == nullptr)
2018-04-02 19:00:36 +02:00
{
SetState(PeepState::Falling);
2018-04-02 19:00:36 +02:00
return;
}
if (ride->GetStation(CurrentRideStation).Exit.IsNull())
2018-04-02 19:00:36 +02:00
{
ride->lifecycle_flags &= ~RIDE_LIFECYCLE_DUE_INSPECTION;
SetState(PeepState::Falling);
2018-04-02 19:00:36 +02:00
return;
}
if (ride->mechanic_status != RIDE_MECHANIC_STATUS_HEADING || !(ride->lifecycle_flags & RIDE_LIFECYCLE_DUE_INSPECTION))
{
SetState(PeepState::Falling);
2018-04-02 19:00:36 +02:00
return;
}
if (SubState == 0)
2018-04-02 19:00:36 +02:00
{
MechanicTimeSinceCall = 0;
2020-09-17 02:22:36 +02:00
ResetPathfindGoal();
SubState = 2;
2018-04-02 19:00:36 +02:00
}
if (SubState <= 3)
2018-04-02 19:00:36 +02:00
{
MechanicTimeSinceCall++;
if (MechanicTimeSinceCall > 2500)
2018-04-02 19:00:36 +02:00
{
if (ride->lifecycle_flags & RIDE_LIFECYCLE_DUE_INSPECTION && ride->mechanic_status == RIDE_MECHANIC_STATUS_HEADING)
{
ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
}
SetState(PeepState::Falling);
2018-04-02 19:00:36 +02:00
return;
}
if (!CheckForPath())
return;
if (ShouldWaitForLevelCrossing() && !IsMechanicHeadingToFixRideBlockingPath())
return;
uint8_t pathingResult;
2018-11-01 13:53:50 +01:00
TileElement* rideEntranceExitElement;
2018-04-02 19:18:15 +02:00
PerformNextAction(pathingResult, rideEntranceExitElement);
2018-04-02 19:00:36 +02:00
if (!(pathingResult & PATHING_RIDE_EXIT) && !(pathingResult & PATHING_RIDE_ENTRANCE))
{
return;
}
if (CurrentRide != rideEntranceExitElement->AsEntrance()->GetRideIndex())
2018-04-02 19:00:36 +02:00
return;
StationIndex exitIndex = rideEntranceExitElement->AsEntrance()->GetStationIndex();
if (CurrentRideStation != exitIndex)
2018-04-02 19:00:36 +02:00
return;
if (pathingResult & PATHING_RIDE_ENTRANCE)
{
if (!ride->GetStation(exitIndex).Exit.IsNull())
2018-04-02 19:00:36 +02:00
{
return;
}
}
PeepDirection = rideEntranceExitElement->GetDirection();
2018-04-02 19:00:36 +02:00
2021-02-20 12:49:50 +01:00
auto newDestination = CoordsXY{ 16, 16 } + NextLoc + (DirectionOffsets[PeepDirection] * 53);
SetDestination(newDestination, 2);
Orientation = PeepDirection << 3;
2018-04-02 19:00:36 +02:00
z = rideEntranceExitElement->BaseHeight * 4;
SubState = 4;
// Falls through into SubState 4
2018-04-02 19:00:36 +02:00
}
2021-02-20 12:49:50 +01:00
int16_t delta_y = abs(GetLocation().y - GetDestination().y);
2021-09-13 18:47:13 +02:00
if (auto loc = UpdateAction(); loc.has_value())
2018-04-02 19:00:36 +02:00
{
auto newZ = ride->GetStation(CurrentRideStation).GetBaseZ();
if (delta_y < 20)
{
newZ += ride->GetRideTypeDescriptor().Heights.PlatformHeight;
}
2018-04-02 19:00:36 +02:00
2021-09-13 18:47:13 +02:00
MoveTo({ loc.value(), newZ });
return;
2018-04-02 19:00:36 +02:00
}
SetState(PeepState::Inspecting);
SubState = 0;
2018-04-02 19:00:36 +02:00
}
/**
*
* rct2: 0x006C0CB8
*/
2019-02-28 20:28:58 +01:00
void Staff::UpdateAnswering()
2018-04-02 19:00:36 +02:00
{
auto ride = GetRide(CurrentRide);
if (ride == nullptr || ride->mechanic_status != RIDE_MECHANIC_STATUS_HEADING)
2018-04-02 19:00:36 +02:00
{
SetState(PeepState::Falling);
2018-04-02 19:00:36 +02:00
return;
}
if (SubState == 0)
2018-04-02 19:00:36 +02:00
{
Action = PeepActionType::StaffAnswerCall;
2020-06-08 01:18:42 +02:00
ActionFrame = 0;
ActionSpriteImageOffset = 0;
2018-04-02 19:00:36 +02:00
UpdateCurrentActionSpriteType();
SubState = 1;
PeepWindowStateUpdate(this);
2018-04-02 19:00:36 +02:00
return;
}
2021-09-15 22:22:15 +02:00
if (SubState == 1)
2018-04-02 19:00:36 +02:00
{
if (IsActionWalking())
2018-04-02 19:00:36 +02:00
{
SubState = 2;
PeepWindowStateUpdate(this);
MechanicTimeSinceCall = 0;
2020-09-17 02:22:36 +02:00
ResetPathfindGoal();
2018-04-02 19:00:36 +02:00
return;
}
UpdateAction();
2019-08-21 12:31:05 +02:00
Invalidate();
2018-04-02 19:00:36 +02:00
return;
}
2021-09-15 22:22:15 +02:00
if (SubState <= 3)
2018-04-02 19:00:36 +02:00
{
MechanicTimeSinceCall++;
if (MechanicTimeSinceCall > 2500)
2018-04-02 19:00:36 +02:00
{
ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
SetState(PeepState::Falling);
2018-04-02 19:00:36 +02:00
return;
}
if (!CheckForPath())
return;
if (ShouldWaitForLevelCrossing() && !IsMechanicHeadingToFixRideBlockingPath())
return;
uint8_t pathingResult;
2018-11-01 13:53:50 +01:00
TileElement* rideEntranceExitElement;
2018-04-02 19:18:15 +02:00
PerformNextAction(pathingResult, rideEntranceExitElement);
2018-04-02 19:00:36 +02:00
if (!(pathingResult & PATHING_RIDE_EXIT) && !(pathingResult & PATHING_RIDE_ENTRANCE))
{
return;
}
2017-12-03 23:45:43 +01:00
if (CurrentRide != rideEntranceExitElement->AsEntrance()->GetRideIndex())
2018-04-02 19:00:36 +02:00
return;
StationIndex exitIndex = rideEntranceExitElement->AsEntrance()->GetStationIndex();
if (CurrentRideStation != exitIndex)
2018-04-02 19:00:36 +02:00
return;
if (pathingResult & PATHING_RIDE_ENTRANCE)
{
if (!ride->GetStation(exitIndex).Exit.IsNull())
2018-04-02 19:00:36 +02:00
{
return;
}
}
PeepDirection = rideEntranceExitElement->GetDirection();
2018-04-02 19:00:36 +02:00
int32_t destX = NextLoc.x + 16 + DirectionOffsets[PeepDirection].x * 53;
int32_t destY = NextLoc.y + 16 + DirectionOffsets[PeepDirection].y * 53;
2018-04-02 19:00:36 +02:00
SetDestination({ destX, destY }, 2);
Orientation = PeepDirection << 3;
2018-04-02 19:00:36 +02:00
z = rideEntranceExitElement->BaseHeight * 4;
SubState = 4;
// Falls through into SubState 4
2018-04-02 19:00:36 +02:00
}
int16_t delta_y = abs(y - GetDestination().y);
2021-09-13 18:47:13 +02:00
if (auto loc = UpdateAction(); loc.has_value())
2018-04-02 19:00:36 +02:00
{
auto newZ = ride->GetStation(CurrentRideStation).GetBaseZ();
if (delta_y < 20)
{
newZ += ride->GetRideTypeDescriptor().Heights.PlatformHeight;
}
2018-04-02 19:00:36 +02:00
2021-09-13 18:47:13 +02:00
MoveTo({ loc.value(), newZ });
return;
2018-04-02 19:00:36 +02:00
}
SetState(PeepState::Fixing);
SubState = 0;
2018-04-02 19:00:36 +02:00
}
/** rct2: 0x00992A5C */
static constexpr CoordsXY _WateringUseOffsets[] = {
2018-04-02 19:00:36 +02:00
{ 3, 16 }, { 16, 29 }, { 29, 16 }, { 16, 3 }, { 3, 29 }, { 29, 29 }, { 29, 3 }, { 3, 3 },
};
/**
*
* rct2: 0x006BF483
*/
bool Staff::UpdatePatrollingFindWatering()
2018-04-02 19:00:36 +02:00
{
if (!(StaffOrders & STAFF_ORDERS_WATER_FLOWERS))
return false;
2018-04-02 19:00:36 +02:00
uint8_t chosen_position = ScenarioRand() & 7;
for (int32_t i = 0; i < 8; ++i, ++chosen_position)
2018-04-02 19:00:36 +02:00
{
chosen_position &= 7;
auto chosenLoc = CoordsXY{ NextLoc } + CoordsDirectionDelta[chosen_position];
2018-04-02 19:00:36 +02:00
TileElement* tile_element = MapGetFirstElementAt(chosenLoc);
2018-04-02 19:00:36 +02:00
// This seems to happen in some SV4 files.
if (tile_element == nullptr)
{
continue;
}
do
{
2021-12-11 00:39:39 +01:00
if (tile_element->GetType() != TileElementType::SmallScenery)
2018-04-02 19:00:36 +02:00
{
continue;
}
auto z_diff = abs(NextLoc.z - tile_element->GetBaseZ());
2018-04-02 19:00:36 +02:00
if (z_diff >= 4 * COORDS_Z_STEP)
2018-04-02 19:00:36 +02:00
{
continue;
}
2021-06-04 03:14:41 +02:00
auto* sceneryEntry = tile_element->AsSmallScenery()->GetEntry();
2018-04-02 19:00:36 +02:00
if (sceneryEntry == nullptr || !sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_CAN_BE_WATERED))
2018-04-02 19:00:36 +02:00
{
continue;
}
if (tile_element->AsSmallScenery()->GetAge() < kSceneryWitherAgeThreshold2)
2018-04-02 19:00:36 +02:00
{
if (chosen_position >= 4)
{
continue;
}
if (tile_element->AsSmallScenery()->GetAge() < kSceneryWitherAgeThreshold1)
2018-04-02 19:00:36 +02:00
{
continue;
}
}
SetState(PeepState::Watering);
Var37 = chosen_position;
2018-04-02 19:00:36 +02:00
SubState = 0;
auto destination = _WateringUseOffsets[chosen_position] + GetLocation().ToTileStart();
SetDestination(destination, 3);
2018-04-02 19:00:36 +02:00
return true;
} while (!(tile_element++)->IsLastForTile());
2018-04-02 19:00:36 +02:00
}
return false;
2018-04-02 19:00:36 +02:00
}
/**
*
* rct2: 0x006BF3A1
*/
bool Staff::UpdatePatrollingFindBin()
2018-04-02 19:00:36 +02:00
{
if (!(StaffOrders & STAFF_ORDERS_EMPTY_BINS))
return false;
2018-04-02 19:00:36 +02:00
if (GetNextIsSurface())
return false;
2018-04-02 19:00:36 +02:00
TileElement* tileElement = MapGetFirstElementAt(NextLoc);
if (tileElement == nullptr)
return false;
2018-04-02 19:00:36 +02:00
for (;; tileElement++)
2018-04-02 19:00:36 +02:00
{
2021-12-11 00:39:39 +01:00
if (tileElement->GetType() == TileElementType::Path && (tileElement->GetBaseZ() == NextLoc.z))
2018-04-02 19:00:36 +02:00
break;
if (tileElement->IsLastForTile())
return false;
2018-04-02 19:00:36 +02:00
}
if (!tileElement->AsPath()->HasAddition())
return false;
auto* pathAddEntry = tileElement->AsPath()->GetAdditionEntry();
if (pathAddEntry == nullptr)
return false;
2018-04-02 19:00:36 +02:00
if (!(pathAddEntry->flags & PATH_ADDITION_FLAG_IS_BIN))
return false;
2018-04-02 19:00:36 +02:00
2019-02-26 10:29:25 +01:00
if (tileElement->AsPath()->IsBroken())
return false;
2018-04-02 19:00:36 +02:00
if (tileElement->AsPath()->AdditionIsGhost())
return false;
2018-04-02 19:00:36 +02:00
uint8_t bin_positions = tileElement->AsPath()->GetEdges();
uint8_t bin_quantity = tileElement->AsPath()->GetAdditionStatus();
uint8_t chosen_position = 0;
2018-04-02 19:00:36 +02:00
for (; chosen_position < 4; ++chosen_position)
{
if (!(bin_positions & 1) && !(bin_quantity & 3))
break;
bin_positions >>= 1;
bin_quantity >>= 2;
}
if (chosen_position == 4)
return false;
2018-04-02 19:00:36 +02:00
Var37 = chosen_position;
SetState(PeepState::EmptyingBin);
2018-04-02 19:00:36 +02:00
SubState = 0;
auto destination = BinUseOffsets[chosen_position] + GetLocation().ToTileStart();
SetDestination(destination, 3);
return true;
2018-04-02 19:00:36 +02:00
}
/**
*
* rct2: 0x006BF322
*/
bool Staff::UpdatePatrollingFindGrass()
2018-04-02 19:00:36 +02:00
{
if (!(StaffOrders & STAFF_ORDERS_MOWING))
return false;
2018-04-02 19:00:36 +02:00
if (StaffMowingTimeout < 12)
return false;
2018-04-02 19:00:36 +02:00
if (!(GetNextIsSurface()))
return false;
2018-04-02 19:00:36 +02:00
auto surfaceElement = MapGetSurfaceElementAt(NextLoc);
if (surfaceElement != nullptr && surfaceElement->CanGrassGrow())
{
if ((surfaceElement->GetGrassLength() & 0x7) >= GRASS_LENGTH_CLEAR_1)
{
SetState(PeepState::Mowing);
Var37 = 0;
// Original code used .y for both x and y. Changed to .x to make more sense (both x and y are 28)
auto destination = _MowingWaypoints[0] + NextLoc;
SetDestination(destination, 3);
return true;
}
}
return false;
2018-04-02 19:00:36 +02:00
}
/**
*
* rct2: 0x006BF295
*/
bool Staff::UpdatePatrollingFindSweeping()
2018-04-02 19:00:36 +02:00
{
if (!(StaffOrders & STAFF_ORDERS_SWEEPING))
return false;
auto quad = EntityTileList<Litter>({ x, y });
for (auto litter : quad)
2018-04-02 19:00:36 +02:00
{
uint16_t z_diff = abs(z - litter->z);
2018-04-02 19:00:36 +02:00
if (z_diff >= 16)
continue;
SetState(PeepState::Sweeping);
Var37 = 0;
SetDestination(litter->GetLocation(), 5);
return true;
2018-04-02 19:00:36 +02:00
}
return false;
2018-04-02 19:00:36 +02:00
}
2019-02-28 20:28:58 +01:00
void Staff::Tick128UpdateStaff()
{
if (AssignedStaffType != StaffType::Security)
return;
PeepSpriteType newSpriteType = PeepSpriteType::SecurityAlt;
if (State != PeepState::Patrolling)
newSpriteType = PeepSpriteType::Security;
if (SpriteType == newSpriteType)
return;
SpriteType = newSpriteType;
ActionSpriteImageOffset = 0;
WalkingFrameNum = 0;
2021-05-11 08:44:41 +02:00
if (Action < PeepActionType::Idle)
Action = PeepActionType::Walking;
2020-05-31 22:45:52 +02:00
PeepFlags &= ~PEEP_FLAGS_SLOW_WALK;
if (gSpriteTypeToSlowWalkMap[EnumValue(newSpriteType)])
{
2020-05-31 22:45:52 +02:00
PeepFlags |= PEEP_FLAGS_SLOW_WALK;
}
ActionSpriteType = PeepActionSpriteType::Invalid;
UpdateCurrentActionSpriteType();
}
2019-02-28 20:28:58 +01:00
bool Staff::IsMechanic() const
{
return AssignedStaffType == StaffType::Mechanic;
}
2019-02-28 20:28:58 +01:00
void Staff::UpdateStaff(uint32_t stepsToTake)
{
switch (State)
2019-02-28 20:28:58 +01:00
{
case PeepState::Patrolling:
2019-02-28 20:28:58 +01:00
UpdatePatrolling();
break;
case PeepState::Mowing:
2019-02-28 20:28:58 +01:00
UpdateMowing();
break;
case PeepState::Sweeping:
2019-02-28 20:28:58 +01:00
UpdateSweeping();
break;
case PeepState::Answering:
2019-02-28 20:28:58 +01:00
UpdateAnswering();
break;
case PeepState::Fixing:
2019-02-28 20:28:58 +01:00
UpdateFixing(stepsToTake);
break;
case PeepState::Inspecting:
2019-02-28 20:28:58 +01:00
UpdateFixing(stepsToTake);
break;
case PeepState::EmptyingBin:
2019-02-28 20:28:58 +01:00
UpdateEmptyingBin();
break;
case PeepState::Watering:
2019-02-28 20:28:58 +01:00
UpdateWatering();
break;
case PeepState::HeadingToInspection:
2019-02-28 20:28:58 +01:00
UpdateHeadingToInspect();
break;
default:
// TODO reset to default state
assert(false);
break;
}
}
2018-04-02 19:00:36 +02:00
/**
*
* rct2: 0x006BF1FD
*/
2019-02-28 20:28:58 +01:00
void Staff::UpdatePatrolling()
2018-04-02 19:00:36 +02:00
{
if (!CheckForPath())
return;
if (ShouldWaitForLevelCrossing() && !IsMechanicHeadingToFixRideBlockingPath())
return;
uint8_t pathingResult;
2018-04-02 19:00:36 +02:00
PerformNextAction(pathingResult);
if (!(pathingResult & PATHING_DESTINATION_REACHED))
return;
if (GetNextIsSurface())
2018-04-02 19:00:36 +02:00
{
auto surfaceElement = MapGetSurfaceElementAt(NextLoc);
2018-04-02 19:00:36 +02:00
if (surfaceElement != nullptr)
2018-04-02 19:00:36 +02:00
{
int32_t water_height = surfaceElement->GetWaterHeight();
if (water_height > 0)
2018-04-02 19:00:36 +02:00
{
2020-05-11 12:36:45 +02:00
MoveTo({ x, y, water_height });
SetState(PeepState::Falling);
2018-04-02 19:00:36 +02:00
return;
}
}
}
if (AssignedStaffType != StaffType::Handyman)
2018-04-02 19:00:36 +02:00
return;
if (UpdatePatrollingFindSweeping())
2018-04-02 19:00:36 +02:00
return;
if (UpdatePatrollingFindGrass())
2018-04-02 19:00:36 +02:00
return;
if (UpdatePatrollingFindBin())
2018-04-02 19:00:36 +02:00
return;
UpdatePatrollingFindWatering();
2018-04-02 19:00:36 +02:00
}
enum
{
2018-06-22 23:04:19 +02:00
PEEP_FIXING_ENTER_STATION = 0,
PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE = 1,
PEEP_FIXING_FIX_VEHICLE_CLOSED_RESTRAINTS = 2,
2018-06-22 23:04:19 +02:00
PEEP_FIXING_FIX_VEHICLE_CLOSED_DOORS = 3,
PEEP_FIXING_FIX_VEHICLE_OPEN_RESTRAINTS = 4,
PEEP_FIXING_FIX_VEHICLE_OPEN_DOORS = 5,
PEEP_FIXING_FIX_VEHICLE_MALFUNCTION = 6,
PEEP_FIXING_MOVE_TO_STATION_END = 7,
PEEP_FIXING_FIX_STATION_END = 8,
PEEP_FIXING_MOVE_TO_STATION_START = 9,
PEEP_FIXING_FIX_STATION_START = 10,
PEEP_FIXING_FIX_STATION_BRAKES = 11,
PEEP_FIXING_MOVE_TO_STATION_EXIT = 12,
PEEP_FIXING_FINISH_FIX_OR_INSPECT = 13,
PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT = 14,
};
/**
* FixingSubstatesForBreakdown[] defines the applicable peep sub_states for
* mechanics fixing a ride. The array is indexed by breakdown_reason:
* - indexes 0-7 are the 8 breakdown reasons (see BREAKDOWN_* in Ride.h)
* when fixing a broken down ride;
* - index 8 is for inspecting a ride.
*/
// clang-format off
static constexpr uint32_t FixingSubstatesForBreakdown[9] = {
2018-06-22 23:04:19 +02:00
( // BREAKDOWN_SAFETY_CUT_OUT
(1 << PEEP_FIXING_MOVE_TO_STATION_END) |
(1 << PEEP_FIXING_FIX_STATION_END) |
(1 << PEEP_FIXING_MOVE_TO_STATION_START) |
(1 << PEEP_FIXING_FIX_STATION_START) |
(1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
(1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
(1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
),
2018-06-22 23:04:19 +02:00
( // BREAKDOWN_RESTRAINTS_STUCK_CLOSED
(1 << PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE) |
(1 << PEEP_FIXING_FIX_VEHICLE_CLOSED_RESTRAINTS) |
(1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
(1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
(1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
),
2018-06-22 23:04:19 +02:00
( // BREAKDOWN_RESTRAINTS_STUCK_OPEN
(1 << PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE) |
(1 << PEEP_FIXING_FIX_VEHICLE_OPEN_RESTRAINTS) |
(1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
(1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
(1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
),
2018-06-22 23:04:19 +02:00
( // BREAKDOWN_DOORS_STUCK_CLOSED
(1 << PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE) |
(1 << PEEP_FIXING_FIX_VEHICLE_CLOSED_DOORS) |
(1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
(1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
(1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
),
2018-06-22 23:04:19 +02:00
( // BREAKDOWN_DOORS_STUCK_OPEN
(1 << PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE) |
(1 << PEEP_FIXING_FIX_VEHICLE_OPEN_DOORS) |
(1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
(1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
(1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
),
2018-06-22 23:04:19 +02:00
( // BREAKDOWN_VEHICLE_MALFUNCTION
(1 << PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE) |
(1 << PEEP_FIXING_FIX_VEHICLE_MALFUNCTION) |
(1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
(1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
(1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
),
2018-06-22 23:04:19 +02:00
( // BREAKDOWN_BRAKES_FAILURE
(1 << PEEP_FIXING_MOVE_TO_STATION_START) |
(1 << PEEP_FIXING_FIX_STATION_BRAKES) |
(1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
(1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
(1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
),
2018-06-22 23:04:19 +02:00
( // BREAKDOWN_CONTROL_FAILURE
(1 << PEEP_FIXING_MOVE_TO_STATION_END) |
(1 << PEEP_FIXING_FIX_STATION_END) |
(1 << PEEP_FIXING_MOVE_TO_STATION_START) |
(1 << PEEP_FIXING_FIX_STATION_START) |
(1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
(1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
(1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
),
2018-06-22 23:04:19 +02:00
( // INSPECTION
(1 << PEEP_FIXING_MOVE_TO_STATION_END) |
(1 << PEEP_FIXING_FIX_STATION_END) |
(1 << PEEP_FIXING_MOVE_TO_STATION_START) |
(1 << PEEP_FIXING_FIX_STATION_START) |
(1 << PEEP_FIXING_MOVE_TO_STATION_EXIT) |
(1 << PEEP_FIXING_FINISH_FIX_OR_INSPECT) |
(1 << PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT)
),
};
// clang-format on
/**
*
* rct2: 0x006C0E8B
* Also used by inspecting.
*/
2019-02-28 20:28:58 +01:00
void Staff::UpdateFixing(int32_t steps)
{
auto ride = GetRide(CurrentRide);
if (ride == nullptr)
{
SetState(PeepState::Falling);
return;
}
bool progressToNextSubstate = true;
2018-06-22 23:04:19 +02:00
bool firstRun = true;
if ((State == PeepState::Inspecting)
2018-06-22 23:04:19 +02:00
&& (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN)))
{
// Ride has broken down since Mechanic was called to inspect it.
// Mechanic identifies the breakdown and switches to fixing it.
State = PeepState::Fixing;
}
while (progressToNextSubstate)
{
switch (SubState)
{
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_ENTER_STATION:
NextFlags &= ~PEEP_NEXT_FLAG_IS_SLOPED;
progressToNextSubstate = UpdateFixingEnterStation(*ride);
2018-06-22 23:04:19 +02:00
break;
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE:
progressToNextSubstate = UpdateFixingMoveToBrokenDownVehicle(firstRun, *ride);
2018-06-22 23:04:19 +02:00
break;
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_FIX_VEHICLE_CLOSED_RESTRAINTS:
case PEEP_FIXING_FIX_VEHICLE_CLOSED_DOORS:
case PEEP_FIXING_FIX_VEHICLE_OPEN_RESTRAINTS:
case PEEP_FIXING_FIX_VEHICLE_OPEN_DOORS:
progressToNextSubstate = UpdateFixingFixVehicle(firstRun, *ride);
2018-06-22 23:04:19 +02:00
break;
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_FIX_VEHICLE_MALFUNCTION:
progressToNextSubstate = UpdateFixingFixVehicleMalfunction(firstRun, *ride);
2018-06-22 23:04:19 +02:00
break;
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_MOVE_TO_STATION_END:
progressToNextSubstate = UpdateFixingMoveToStationEnd(firstRun, *ride);
2018-06-22 23:04:19 +02:00
break;
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_FIX_STATION_END:
progressToNextSubstate = UpdateFixingFixStationEnd(firstRun);
break;
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_MOVE_TO_STATION_START:
progressToNextSubstate = UpdateFixingMoveToStationStart(firstRun, *ride);
2018-06-22 23:04:19 +02:00
break;
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_FIX_STATION_START:
progressToNextSubstate = UpdateFixingFixStationStart(firstRun, *ride);
2018-06-22 23:04:19 +02:00
break;
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_FIX_STATION_BRAKES:
progressToNextSubstate = UpdateFixingFixStationBrakes(firstRun, *ride);
2018-06-22 23:04:19 +02:00
break;
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_MOVE_TO_STATION_EXIT:
progressToNextSubstate = UpdateFixingMoveToStationExit(firstRun, *ride);
2018-06-22 23:04:19 +02:00
break;
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_FINISH_FIX_OR_INSPECT:
progressToNextSubstate = UpdateFixingFinishFixOrInspect(firstRun, steps, *ride);
2018-06-22 23:04:19 +02:00
break;
2018-06-22 23:04:19 +02:00
case PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT:
progressToNextSubstate = UpdateFixingLeaveByEntranceExit(firstRun, *ride);
2018-06-22 23:04:19 +02:00
break;
2018-06-22 23:04:19 +02:00
default:
LOG_ERROR("Invalid substate");
2018-06-22 23:04:19 +02:00
progressToNextSubstate = false;
}
firstRun = false;
if (!progressToNextSubstate)
{
break;
}
int32_t subState = SubState;
uint32_t sub_state_sequence_mask = FixingSubstatesForBreakdown[8];
if (State != PeepState::Inspecting)
{
sub_state_sequence_mask = FixingSubstatesForBreakdown[ride->breakdown_reason_pending];
}
do
{
subState++;
} while ((sub_state_sequence_mask & (1 << subState)) == 0);
SubState = subState & 0xFF;
}
}
/**
* rct2: 0x006C0EEC
* fixing SubState: enter_station - applies to fixing all break down reasons and ride inspections.
*/
bool Staff::UpdateFixingEnterStation(Ride& ride) const
{
ride.mechanic_status = RIDE_MECHANIC_STATUS_FIXING;
ride.window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
return true;
}
/**
* rct2: 0x006C0F09
* fixing SubState: move_to_broken_down_vehicle - applies to fixing all vehicle specific breakdown reasons
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingMoveToBrokenDownVehicle(bool firstRun, const Ride& ride)
{
if (!firstRun)
{
Vehicle* vehicle = RideGetBrokenVehicle(ride);
if (vehicle == nullptr)
{
return true;
}
while (true)
{
2019-02-25 18:58:22 +01:00
if (vehicle->IsHead())
{
break;
}
2021-01-22 11:33:55 +01:00
auto trackType = vehicle->GetTrackType();
if (TrackTypeIsStation(trackType))
{
break;
}
vehicle = GetEntity<Vehicle>(vehicle->prev_vehicle_on_ride);
if (vehicle == nullptr)
{
return true;
}
}
CoordsXY offset = DirectionOffsets[PeepDirection];
auto destination = (offset * -12) + vehicle->GetLocation();
SetDestination(destination, 2);
}
2021-09-13 18:47:13 +02:00
if (auto loc = UpdateAction(); loc.has_value())
{
2021-09-13 18:47:13 +02:00
MoveTo({ loc.value(), z });
return false;
}
return true;
}
/**
* rct2: 0x006C0FD3
* fixing SubState: fix_vehicle - applies to fixing vehicle with:
* 1. restraints stuck closed,
* 2. doors stuck closed,
* 3. restrains stuck open,
* 4. doors stuck open.
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingFixVehicle(bool firstRun, const Ride& ride)
{
if (!firstRun)
{
Orientation = PeepDirection << 3;
Action = (ScenarioRand() & 1) ? PeepActionType::StaffFix2 : PeepActionType::StaffFix;
ActionSpriteImageOffset = 0;
2020-06-08 01:18:42 +02:00
ActionFrame = 0;
UpdateCurrentActionSpriteType();
}
if (IsActionWalking())
{
return true;
}
UpdateAction();
2019-08-21 12:31:05 +02:00
Invalidate();
uint8_t actionFrame = (Action == PeepActionType::StaffFix) ? 0x25 : 0x50;
2020-06-08 01:18:42 +02:00
if (ActionFrame != actionFrame)
{
return false;
}
Vehicle* vehicle = RideGetBrokenVehicle(ride);
if (vehicle == nullptr)
{
return true;
}
vehicle->ClearFlag(VehicleFlags::CarIsBroken);
return false;
}
/**
* rct2: 0x006C107B
* fixing SubState: fix_vehicle_malfunction - applies fixing to vehicle malfunction.
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingFixVehicleMalfunction(bool firstRun, const Ride& ride)
{
if (!firstRun)
{
Orientation = PeepDirection << 3;
Action = PeepActionType::StaffFix3;
ActionSpriteImageOffset = 0;
2020-06-08 01:18:42 +02:00
ActionFrame = 0;
UpdateCurrentActionSpriteType();
}
if (IsActionWalking())
{
return true;
}
UpdateAction();
2019-08-21 12:31:05 +02:00
Invalidate();
2020-06-08 01:18:42 +02:00
if (ActionFrame != 0x65)
{
return false;
}
Vehicle* vehicle = RideGetBrokenVehicle(ride);
if (vehicle == nullptr)
{
return true;
}
vehicle->ClearFlag(VehicleFlags::TrainIsBroken);
return false;
}
/** rct2: 0x00992A3C */
static constexpr CoordsXY _StationFixingOffsets[] = {
{ -12, 0 },
{ 0, 12 },
{ 12, 0 },
{ 0, -12 },
};
/**
* rct2: 0x006C1114
* fixing SubState: move_to_station_end - applies to fixing station specific breakdowns: safety cut-out, control failure,
2018-06-22 23:04:19 +02:00
* inspection.
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingMoveToStationEnd(bool firstRun, const Ride& ride)
{
if (!firstRun)
{
if (ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION)
|| !ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))
{
return true;
}
auto stationPos = ride.GetStation(CurrentRideStation).GetStart();
if (stationPos.IsNull())
{
return true;
}
auto tileElement = MapGetTrackElementAt(stationPos);
if (tileElement == nullptr)
{
LOG_ERROR("Couldn't find tile_element");
return false;
}
int32_t trackDirection = tileElement->GetDirection();
2018-06-22 23:04:19 +02:00
CoordsXY offset = _StationFixingOffsets[trackDirection];
stationPos.x += 16 + offset.x;
if (offset.x == 0)
{
stationPos.x = GetDestination().x;
}
stationPos.y += 16 + offset.y;
if (offset.y == 0)
{
stationPos.y = GetDestination().y;
}
SetDestination(stationPos, 2);
}
2021-09-13 18:47:13 +02:00
if (auto loc = UpdateAction(); loc.has_value())
{
2021-09-13 18:47:13 +02:00
MoveTo({ loc.value(), z });
return false;
}
return true;
}
/**
* rct2: 0x006C11F5
* fixing SubState: fix_station_end - applies to fixing station specific breakdowns: safety cut-out, control failure,
2018-06-22 23:04:19 +02:00
* inspection.
* - see FixingSubstatesForBreakdown[]
*/
2019-02-28 20:28:58 +01:00
bool Staff::UpdateFixingFixStationEnd(bool firstRun)
{
if (!firstRun)
{
Orientation = PeepDirection << 3;
Action = PeepActionType::StaffCheckboard;
2020-06-08 01:18:42 +02:00
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
}
if (IsActionWalking())
{
return true;
}
UpdateAction();
2019-08-21 12:31:05 +02:00
Invalidate();
return false;
}
/**
* rct2: 0x006C1239
* fixing SubState: move_to_station_start
* 1. applies to fixing station specific breakdowns: safety cut-out, control failure,
* 2. applies to fixing brake failure,
* 3. applies to inspection.
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingMoveToStationStart(bool firstRun, const Ride& ride)
{
if (!firstRun)
{
if (ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION)
|| !ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))
{
return true;
}
auto stationPosition = ride.GetStation(CurrentRideStation).GetStart();
if (stationPosition.IsNull())
{
return true;
}
CoordsXYE input;
input.x = stationPosition.x;
input.y = stationPosition.y;
input.element = MapGetTrackElementAtFromRide({ input.x, input.y, stationPosition.z }, CurrentRide);
if (input.element == nullptr)
{
return true;
}
Direction stationDirection = 0;
TrackBeginEnd trackBeginEnd;
while (TrackBlockGetPrevious(input, &trackBeginEnd))
{
if (trackBeginEnd.begin_element->AsTrack()->IsStation())
{
2018-06-22 23:04:19 +02:00
input.x = trackBeginEnd.begin_x;
input.y = trackBeginEnd.begin_y;
input.element = trackBeginEnd.begin_element;
stationDirection = trackBeginEnd.begin_element->GetDirection();
continue;
}
break;
}
2023-01-17 19:05:14 +01:00
// Loc6C12ED:
auto destination = CoordsXY{ input.x + 16, input.y + 16 };
auto offset = _StationFixingOffsets[stationDirection];
destination.x -= offset.x;
if (offset.x == 0)
{
destination.x = GetDestination().x;
}
destination.y -= offset.y;
if (offset.y == 0)
{
destination.y = GetDestination().y;
}
SetDestination(destination, 2);
}
2021-09-13 18:47:13 +02:00
if (auto loc = UpdateAction(); loc.has_value())
{
2021-09-13 18:47:13 +02:00
MoveTo({ loc.value(), z });
return false;
}
return true;
}
/**
* rct2: 0x006C1368
* fixing SubState: fix_station_start
* 1. applies to fixing station specific breakdowns: safety cut-out, control failure,
* 2. applies to inspection.
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingFixStationStart(bool firstRun, const Ride& ride)
{
if (!firstRun)
{
if (ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION)
|| !ride.GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))
{
return true;
}
Orientation = PeepDirection << 3;
Action = PeepActionType::StaffFix;
2020-06-08 01:18:42 +02:00
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
}
if (IsActionWalking())
{
return true;
}
UpdateAction();
return false;
}
/**
* rct2: 0x006C13CE
* fixing SubState: fix_station_brakes - applies to fixing brake failure
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingFixStationBrakes(bool firstRun, Ride& ride)
{
if (!firstRun)
{
Orientation = PeepDirection << 3;
Action = PeepActionType::StaffFixGround;
2020-06-08 01:18:42 +02:00
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
}
if (IsActionWalking())
{
return true;
}
UpdateAction();
2019-08-21 12:31:05 +02:00
Invalidate();
2020-06-08 01:18:42 +02:00
if (ActionFrame == 0x28)
{
ride.mechanic_status = RIDE_MECHANIC_STATUS_HAS_FIXED_STATION_BRAKES;
ride.window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
}
2020-06-08 01:18:42 +02:00
if (ActionFrame == 0x13 || ActionFrame == 0x19 || ActionFrame == 0x1F || ActionFrame == 0x25 || ActionFrame == 0x2B)
{
2021-09-28 02:16:04 +02:00
OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::MechanicFix, GetLocation());
}
return false;
}
/**
* rct2: 0x006C1474
* fixing SubState: move_to_station_exit - applies to fixing all failures & inspections
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingMoveToStationExit(bool firstRun, const Ride& ride)
{
if (!firstRun)
{
auto stationPosition = ride.GetStation(CurrentRideStation).Exit.ToCoordsXY();
if (stationPosition.IsNull())
{
stationPosition = ride.GetStation(CurrentRideStation).Entrance.ToCoordsXY();
if (stationPosition.IsNull())
{
return true;
}
}
2020-03-13 12:03:43 +01:00
stationPosition = stationPosition.ToTileCentre();
CoordsXY stationPlatformDirection = DirectionOffsets[PeepDirection];
2020-03-13 12:03:43 +01:00
stationPosition.x += stationPlatformDirection.x * 20;
stationPosition.y += stationPlatformDirection.y * 20;
SetDestination(stationPosition, 2);
}
2021-09-13 18:47:13 +02:00
if (auto loc = UpdateAction(); loc.has_value())
{
2021-09-13 18:47:13 +02:00
MoveTo({ loc.value(), z });
return false;
}
2021-09-15 22:22:15 +02:00
return true;
}
/**
* rct2: 0x006C1504
* fixing SubState: finish_fix_or_inspect - applies to fixing all failures & inspections
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingFinishFixOrInspect(bool firstRun, int32_t steps, Ride& ride)
{
if (!firstRun)
{
if (State == PeepState::Inspecting)
{
UpdateRideInspected(CurrentRide);
StaffRidesInspected++;
WindowInvalidateFlags |= RIDE_INVALIDATE_RIDE_INCOME | RIDE_INVALIDATE_RIDE_LIST;
ride.mechanic_status = RIDE_MECHANIC_STATUS_UNDEFINED;
return true;
}
StaffRidesFixed++;
WindowInvalidateFlags |= RIDE_INVALIDATE_RIDE_INCOME | RIDE_INVALIDATE_RIDE_LIST;
Orientation = PeepDirection << 3;
Action = PeepActionType::StaffAnswerCall2;
2020-06-08 01:18:42 +02:00
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
}
if (!IsActionWalking())
{
UpdateAction();
2019-08-21 12:31:05 +02:00
Invalidate();
return false;
}
RideFixBreakdown(ride, steps);
ride.mechanic_status = RIDE_MECHANIC_STATUS_UNDEFINED;
return true;
}
/**
* rct2: 0x006C157E
* fixing SubState: leave_by_entrance_exit - applies to fixing all failures & inspections
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingLeaveByEntranceExit(bool firstRun, const Ride& ride)
{
if (!firstRun)
{
auto exitPosition = ride.GetStation(CurrentRideStation).Exit.ToCoordsXY();
if (exitPosition.IsNull())
{
exitPosition = ride.GetStation(CurrentRideStation).Entrance.ToCoordsXY();
if (exitPosition.IsNull())
{
SetState(PeepState::Falling);
return false;
}
}
2020-03-13 12:03:43 +01:00
exitPosition = exitPosition.ToTileCentre();
CoordsXY ebx_direction = DirectionOffsets[PeepDirection];
2020-03-13 12:03:43 +01:00
exitPosition.x -= ebx_direction.x * 19;
exitPosition.y -= ebx_direction.y * 19;
SetDestination(exitPosition, 2);
}
int16_t xy_distance;
2021-09-13 18:47:13 +02:00
if (auto loc = UpdateAction(xy_distance); loc.has_value())
{
auto stationHeight = ride.GetStation(CurrentRideStation).GetBaseZ();
if (xy_distance >= 16)
{
stationHeight += ride.GetRideTypeDescriptor().Heights.PlatformHeight;
}
2021-09-13 18:47:13 +02:00
MoveTo({ loc.value(), stationHeight });
return false;
}
SetState(PeepState::Falling);
return false;
}
/**
* rct2: 0x6B7588
*/
2022-01-19 14:17:11 +01:00
void Staff::UpdateRideInspected(RideId rideIndex)
{
auto ride = GetRide(rideIndex);
if (ride != nullptr)
{
ride->lifecycle_flags &= ~RIDE_LIFECYCLE_DUE_INSPECTION;
ride->reliability += ((100 - ride->reliability_percentage) / 4) * (ScenarioRand() & 0xFF);
ride->last_inspection = 0;
ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE | RIDE_INVALIDATE_RIDE_MAIN
| RIDE_INVALIDATE_RIDE_LIST;
}
}
money64 GetStaffWage(StaffType type)
{
switch (type)
{
default:
case StaffType::Handyman:
return 50.00_GBP;
case StaffType::Mechanic:
return 80.00_GBP;
case StaffType::Security:
return 60.00_GBP;
case StaffType::Entertainer:
return 55.00_GBP;
}
}
void Staff::Serialise(DataSerialiser& stream)
{
Peep::Serialise(stream);
stream << AssignedStaffType;
stream << MechanicTimeSinceCall;
stream << HireDate;
stream << StaffOrders;
stream << StaffMowingTimeout;
stream << StaffLawnsMown;
stream << StaffGardensWatered;
stream << StaffLitterSwept;
stream << StaffBinsEmptied;
}