1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-18 04:23:20 +01:00
Files
OpenRCT2/src/openrct2/peep/Staff.cpp

2701 lines
74 KiB
C++

/*****************************************************************************
* Copyright (c) 2014-2019 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.
*****************************************************************************/
#include "Staff.h"
#include "../Context.h"
#include "../Game.h"
#include "../Input.h"
#include "../actions/StaffHireNewAction.hpp"
#include "../actions/StaffSetOrdersAction.hpp"
#include "../audio/audio.h"
#include "../config/Config.h"
#include "../interface/Viewport.h"
#include "../localisation/Date.h"
#include "../localisation/Localisation.h"
#include "../localisation/StringIds.h"
#include "../management/Finance.h"
#include "../network/network.h"
#include "../object/ObjectList.h"
#include "../object/ObjectManager.h"
#include "../object/TerrainSurfaceObject.h"
#include "../paint/tile_element/Paint.TileElement.h"
#include "../ride/RideData.h"
#include "../ride/Station.h"
#include "../ride/Track.h"
#include "../scenario/Scenario.h"
#include "../util/Util.h"
#include "../world/Entrance.h"
#include "../world/Footpath.h"
#include "../world/Scenery.h"
#include "../world/SmallScenery.h"
#include "../world/Sprite.h"
#include "../world/Surface.h"
#include "Peep.h"
#include <algorithm>
#include <iterator>
/**
* Monthly staff wages
*
* rct2: 0x00992A00
*/
const money32 gStaffWageTable[STAFF_TYPE_COUNT] = {
MONEY(50, 00), // Handyman
MONEY(80, 00), // Mechanic
MONEY(60, 00), // Security guard
MONEY(55, 00), // Entertainer
};
// clang-format off
const rct_string_id 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,
};
// clang-format on
// Every staff member has STAFF_PATROL_AREA_SIZE elements assigned to in this array, indexed by their StaffId
// Additionally there is a patrol area for each staff type, which is the union of the patrols of all staff members of that type
uint32_t gStaffPatrolAreas[(STAFF_MAX_COUNT + STAFF_TYPE_COUNT) * STAFF_PATROL_AREA_SIZE];
uint8_t gStaffModes[STAFF_MAX_COUNT + STAFF_TYPE_COUNT];
uint16_t gStaffDrawPatrolAreas;
colour_t gStaffHandymanColour;
colour_t gStaffMechanicColour;
colour_t gStaffSecurityColour;
template<> bool SpriteBase::Is<Staff>() const
{
auto peep = As<Peep>();
return peep && peep->AssignedPeepType == PEEP_TYPE_STAFF;
}
/**
*
* rct2: 0x006BD3A4
*/
void staff_reset_modes()
{
for (int32_t i = 0; i < STAFF_MAX_COUNT; i++)
gStaffModes[i] = STAFF_MODE_NONE;
for (int32_t i = STAFF_MAX_COUNT; i < (STAFF_MAX_COUNT + STAFF_TYPE_COUNT); i++)
gStaffModes[i] = STAFF_MODE_WALK;
staff_update_greyed_patrol_areas();
}
/**
* Hires a new staff member of the given type.
*/
bool staff_hire_new_member(STAFF_TYPE staffType, ENTERTAINER_COSTUME entertainerType)
{
bool autoPosition = gConfigGeneral.auto_staff_placement;
if (gInputPlaceObjectModifier & PLACE_OBJECT_MODIFIER_SHIFT_Z)
{
autoPosition = autoPosition ^ 1;
}
uint32_t staffOrders = 0;
if (staffType == STAFF_TYPE_HANDYMAN)
{
staffOrders = STAFF_ORDERS_SWEEPING | STAFF_ORDERS_WATER_FLOWERS | STAFF_ORDERS_EMPTY_BINS;
if (gConfigGeneral.handymen_mow_default)
{
staffOrders |= STAFF_ORDERS_MOWING;
}
}
else if (staffType == STAFF_TYPE_MECHANIC)
{
staffOrders = STAFF_ORDERS_INSPECT_RIDES | STAFF_ORDERS_FIX_RIDES;
}
auto hireStaffAction = StaffHireNewAction(autoPosition, staffType, entertainerType, staffOrders);
hireStaffAction.SetCallback([=](const GameAction*, const StaffHireNewActionResult* res) -> void {
if (res->Error != GA_ERROR::OK)
return;
// Open window for new staff.
auto peep = GetEntity<Peep>(res->peepSriteIndex);
auto intent = Intent(WC_PEEP);
intent.putExtra(INTENT_EXTRA_PEEP, peep);
context_open_intent(&intent);
});
auto res = GameActions::Execute(&hireStaffAction);
return res->Error == GA_ERROR::OK;
}
/**
*
* rct2: 0x006C0C3F
*/
void staff_update_greyed_patrol_areas()
{
for (int32_t staff_type = 0; staff_type < STAFF_TYPE_COUNT; ++staff_type)
{
int32_t staffPatrolOffset = (staff_type + STAFF_MAX_COUNT) * STAFF_PATROL_AREA_SIZE;
for (int32_t i = 0; i < STAFF_PATROL_AREA_SIZE; i++)
{
gStaffPatrolAreas[staffPatrolOffset + i] = 0;
}
for (auto peep : EntityList<Staff>(EntityListId::Peep))
{
if (peep->StaffType == staff_type)
{
int32_t peepPatrolOffset = peep->StaffId * STAFF_PATROL_AREA_SIZE;
for (int32_t i = 0; i < STAFF_PATROL_AREA_SIZE; i++)
{
gStaffPatrolAreas[staffPatrolOffset + i] |= gStaffPatrolAreas[peepPatrolOffset + i];
}
}
}
}
}
/**
*
* rct2: 0x006C0905
*/
bool Staff::IsLocationInPatrol(const CoordsXY& loc) const
{
// Check if location is in the park
if (!map_is_location_owned_or_has_rights(loc))
return false;
// Check if staff has patrol area
if (!(gStaffModes[StaffId] & 2))
return true;
return IsPatrolAreaSet(loc);
}
bool staff_is_location_on_patrol_edge(Peep* mechanic, const CoordsXY& loc)
{
// Check whether the location x,y is inside and on the edge of the
// patrol zone for mechanic.
bool onZoneEdge = false;
int32_t neighbourDir = 0;
while (!onZoneEdge && neighbourDir <= 7)
{
auto neighbourPos = loc + CoordsDirectionDelta[neighbourDir];
onZoneEdge = !mechanic->AsStaff()->IsLocationInPatrol(neighbourPos);
neighbourDir++;
}
return onZoneEdge;
}
bool staff_can_ignore_wide_flag(Peep* staff, const CoordsXYZ& staffPos, TileElement* path)
{
/* 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 (staff->AssignedPeepType != PEEP_TYPE_STAFF)
return false;
if (!staff_is_location_on_patrol_edge(staff, 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. */
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 (!staff->AsStaff()->IsLocationInPatrol(adjacPos))
continue;
/* Ignore adjacent tiles on the patrol zone edge. */
if (staff_is_location_on_patrol_edge(staff, 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;
}
if (path->AsPath()->IsSloped())
{
if (path->AsPath()->GetSlopeDirection() == adjac_dir)
{
adjacPos.z += PATH_HEIGHT_STEP;
}
}
/* Search through all adjacent map elements */
TileElement* test_element = map_get_first_element_at(adjacPos);
if (test_element == nullptr)
return false;
bool pathfound = false;
bool widefound = false;
do
{
if (test_element->GetType() != TILE_ELEMENT_TYPE_PATH)
{
continue;
}
/* test_element is a path */
if (!is_valid_path_z_and_direction(test_element, adjacPos.z / COORDS_Z_STEP, adjac_dir))
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)
{
case 0: /* Concave corner */
return true;
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
*/
static uint8_t staff_get_valid_patrol_directions(Staff* staff, const CoordsXY& loc)
{
uint8_t directions = 0;
if (staff->IsLocationInPatrol({ loc.x - COORDS_XY_STEP, loc.y }))
{
directions |= (1 << 0);
}
if (staff->IsLocationInPatrol({ loc.x, loc.y + COORDS_XY_STEP }))
{
directions |= (1 << 1);
}
if (staff->IsLocationInPatrol({ loc.x + COORDS_XY_STEP, loc.y }))
{
directions |= (1 << 2);
}
if (staff->IsLocationInPatrol({ loc.x, loc.y - COORDS_XY_STEP }))
{
directions |= (1 << 3);
}
if (directions == 0)
{
directions = 0xF;
}
return directions;
}
/**
*
* rct2: 0x006C1955
*/
void staff_reset_stats()
{
for (auto peep : EntityList<Staff>(EntityListId::Peep))
{
peep->TimeInPark = gDateMonthsElapsed;
peep->StaffLawnsMown = 0;
peep->StaffRidesFixed = 0;
peep->StaffGardensWatered = 0;
peep->StaffRidesInspected = 0;
peep->StaffLitterSwept = 0;
peep->StaffBinsEmptied = 0;
}
}
static std::pair<int32_t, int32_t> getPatrolAreaOffsetIndex(const CoordsXY& coords)
{
// Patrol areas are 4 * 4 tiles (32 * 4) = 128 = 2^^7
auto hash = ((coords.x & 0x1F80) >> 7) | ((coords.y & 0x1F80) >> 1);
return { hash >> 5, hash & 0x1F };
}
static bool staff_is_patrol_area_set(int32_t staffIndex, const CoordsXY& coords)
{
// Patrol quads are stored in a bit map (8 patrol quads per byte).
// Each patrol quad is 4x4.
// Therefore there are in total 64 x 64 patrol quads in the 256 x 256 map.
// At the end of the array (after the slots for individual staff members),
// there are slots that save the combined patrol area for every staff type.
int32_t peepOffset = staffIndex * STAFF_PATROL_AREA_SIZE;
auto [offset, bitIndex] = getPatrolAreaOffsetIndex(coords);
return gStaffPatrolAreas[peepOffset + offset] & (1UL << bitIndex);
}
bool Staff::IsPatrolAreaSet(const CoordsXY& coords) const
{
return staff_is_patrol_area_set(StaffId, coords);
}
bool staff_is_patrol_area_set_for_type(STAFF_TYPE type, const CoordsXY& coords)
{
return staff_is_patrol_area_set(STAFF_MAX_COUNT + type, coords);
}
void staff_set_patrol_area(int32_t staffIndex, const CoordsXY& coords, bool value)
{
int32_t peepOffset = staffIndex * STAFF_PATROL_AREA_SIZE;
auto [offset, bitIndex] = getPatrolAreaOffsetIndex(coords);
uint32_t* addr = &gStaffPatrolAreas[peepOffset + offset];
if (value)
{
*addr |= (1 << bitIndex);
}
else
{
*addr &= ~(1 << bitIndex);
}
}
void staff_toggle_patrol_area(int32_t staffIndex, const CoordsXY& coords)
{
int32_t peepOffset = staffIndex * STAFF_PATROL_AREA_SIZE;
auto [offset, bitIndex] = getPatrolAreaOffsetIndex(coords);
gStaffPatrolAreas[peepOffset + offset] ^= (1 << bitIndex);
}
/**
*
* rct2: 0x006BFBE8
*
* Returns INVALID_DIRECTION when no nearby litter or unpathable litter
*/
static uint8_t staff_handyman_direction_to_nearest_litter(Peep* peep)
{
uint16_t nearestLitterDist = 0xFFFF;
Litter* nearestLitter = nullptr;
for (auto litter : EntityList<Litter>(EntityListId::Litter))
{
uint16_t distance = abs(litter->x - peep->x) + abs(litter->y - peep->y) + abs(litter->z - peep->z) * 4;
if (distance < nearestLitterDist)
{
nearestLitterDist = distance;
nearestLitter = litter;
}
}
if (nearestLitterDist > 0x60)
{
return INVALID_DIRECTION;
}
auto litterTile = CoordsXY{ nearestLitter->x, nearestLitter->y }.ToTileStart();
if (!peep->AsStaff()->IsLocationInPatrol(litterTile))
{
return INVALID_DIRECTION;
}
litterTile = litterTile.ToTileCentre();
int16_t x_diff = litterTile.x - peep->x;
int16_t y_diff = litterTile.y - peep->y;
uint8_t nextDirection = 0;
if (abs(x_diff) <= abs(y_diff))
{
nextDirection = y_diff < 0 ? 3 : 1;
}
else
{
nextDirection = x_diff < 0 ? 0 : 2;
}
CoordsXY nextTile = { static_cast<int32_t>((nearestLitter->x & 0xFFE0) - CoordsDirectionDelta[nextDirection].x),
static_cast<int32_t>((nearestLitter->y & 0xFFE0) - CoordsDirectionDelta[nextDirection].y) };
int16_t nextZ = ((peep->z + 8) & 0xFFF0) / 8;
TileElement* tileElement = map_get_first_element_at(nextTile);
if (tileElement == nullptr)
return INVALID_DIRECTION;
do
{
if (tileElement->base_height != nextZ)
continue;
if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE || tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
{
return INVALID_DIRECTION;
}
} while (!(tileElement++)->IsLastForTile());
nextTile.x = (peep->x & 0xFFE0) + CoordsDirectionDelta[nextDirection].x;
nextTile.y = (peep->y & 0xFFE0) + CoordsDirectionDelta[nextDirection].y;
tileElement = map_get_first_element_at(nextTile);
if (tileElement == nullptr)
return INVALID_DIRECTION;
do
{
if (tileElement->base_height != nextZ)
continue;
if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE || tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
{
return INVALID_DIRECTION;
}
} while (!(tileElement++)->IsLastForTile());
return nextDirection;
}
/**
*
* rct2: 0x006BF931
*/
static uint8_t staff_handyman_direction_to_uncut_grass(Peep* peep, uint8_t valid_directions)
{
if (!(peep->GetNextIsSurface()))
{
auto surfaceElement = map_get_surface_element_at(peep->NextLoc);
if (surfaceElement == nullptr)
return INVALID_DIRECTION;
if (peep->NextLoc.z != surfaceElement->GetBaseZ())
return INVALID_DIRECTION;
if (peep->GetNextIsSloped())
{
if (surfaceElement->GetSlope() != PathSlopeToLandSlope[peep->GetNextDirection()])
return INVALID_DIRECTION;
}
else if (surfaceElement->GetSlope() != TILE_ELEMENT_SLOPE_FLAT)
return INVALID_DIRECTION;
}
uint8_t chosenDirection = scenario_rand() & 0x3;
for (uint8_t i = 0; i < 4; ++i, ++chosenDirection)
{
chosenDirection &= 0x3;
if (!(valid_directions & (1 << chosenDirection)))
{
continue;
}
CoordsXY chosenTile = CoordsXY{ peep->NextLoc } + CoordsDirectionDelta[chosenDirection];
if (!map_is_location_valid(chosenTile))
continue;
auto surfaceElement = map_get_surface_element_at(chosenTile);
if (surfaceElement != nullptr)
{
if (std::abs(surfaceElement->GetBaseZ() - peep->NextLoc.z) <= 2 * COORDS_Z_STEP)
{
if (surfaceElement->CanGrassGrow() && (surfaceElement->GetGrassLength() & 0x7) >= GRASS_LENGTH_CLEAR_1)
{
return chosenDirection;
}
}
}
}
return INVALID_DIRECTION;
}
/**
*
* rct2: 0x006BFD9C
*/
int32_t Staff::HandymanDirectionRandSurface(uint8_t validDirections)
{
uint8_t newDirection = scenario_rand() % NumOrthogonalDirections;
for (int32_t i = 0; i < NumOrthogonalDirections; ++i, ++newDirection)
{
newDirection %= NumOrthogonalDirections;
if (!(validDirections & (1 << newDirection)))
continue;
CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
if (map_surface_is_blocked(chosenTile))
continue;
break;
}
// If it tries all directions this is required
// to make it back to the first direction and
// override validDirections
newDirection %= NumOrthogonalDirections;
return newDirection;
}
/**
*
* rct2: 0x006BFBA8
*/
bool Staff::DoHandymanPathFinding()
{
StaffMowingTimeout++;
uint8_t litterDirection = INVALID_DIRECTION;
uint8_t validDirections = staff_get_valid_patrol_directions(this, NextLoc);
if ((StaffOrders & STAFF_ORDERS_SWEEPING) && ((gCurrentTicks + sprite_index) & 0xFFF) > 110)
{
litterDirection = staff_handyman_direction_to_nearest_litter(this);
}
Direction newDirection = INVALID_DIRECTION;
if (litterDirection == INVALID_DIRECTION && (StaffOrders & STAFF_ORDERS_MOWING) && StaffMowingTimeout >= 12)
{
newDirection = staff_handyman_direction_to_uncut_grass(this, validDirections);
}
if (newDirection == INVALID_DIRECTION)
{
if (GetNextIsSurface())
{
newDirection = HandymanDirectionRandSurface(validDirections);
}
else
{
auto* pathElement = map_get_path_element_at(TileCoordsXYZ{ NextLoc });
if (pathElement == nullptr)
return true;
uint8_t pathDirections = (pathElement->GetEdges() & validDirections) & 0xF;
if (pathDirections == 0)
{
newDirection = HandymanDirectionRandSurface(validDirections);
}
else
{
bool chooseRandom = true;
if (litterDirection != INVALID_DIRECTION && pathDirections & (1 << litterDirection))
{
/// Check whether path is a queue path and connected to a ride
bool connectedQueue = (pathElement->IsQueue() && pathElement->GetRideIndex() != RIDE_ID_NULL);
/// 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 ((scenario_rand() & 0xFFFF) >= chooseRandomProbability)
{
chooseRandom = false;
newDirection = litterDirection;
}
}
else
{
pathDirections &= ~(1 << direction_reverse(PeepDirection));
if (pathDirections == 0)
{
pathDirections |= 1 << direction_reverse(PeepDirection);
}
}
if (chooseRandom)
{
do
{
newDirection = scenario_rand() & 3;
} while ((pathDirections & (1 << newDirection)) == 0);
}
}
}
}
// countof(CoordsDirectionDelta)
assert(newDirection < 8);
CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
while (!map_is_location_valid(chosenTile))
{
newDirection = HandymanDirectionRandSurface(validDirections);
chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
}
PeepDirection = newDirection;
DestinationX = chosenTile.x + 16;
DestinationY = chosenTile.y + 16;
DestinationTolerance = 3;
if (State == PEEP_STATE_QUEUING)
{
DestinationTolerance = (scenario_rand() & 7) + 2;
}
return false;
}
static uint8_t staff_direction_surface(Peep* peep, uint8_t initialDirection)
{
uint8_t direction = initialDirection;
for (int32_t i = 0; i < 3; ++i)
{
// Looks left and right from initial direction
switch (i)
{
case 1:
direction++;
if (scenario_rand() & 1)
{
direction -= 2;
}
break;
case 2:
direction -= 2;
break;
}
direction &= 3;
if (fence_in_the_way({ peep->NextLoc, peep->NextLoc.z, peep->NextLoc.z + PEEP_CLEARANCE_HEIGHT }, direction))
continue;
if (fence_in_the_way(
{ peep->NextLoc, peep->NextLoc.z, peep->NextLoc.z + PEEP_CLEARANCE_HEIGHT }, direction_reverse(direction)))
continue;
CoordsXY chosenTile = CoordsXY{ peep->NextLoc } + CoordsDirectionDelta[direction];
if (!map_surface_is_blocked(chosenTile))
{
return direction;
}
}
return initialDirection;
}
/**
*
* rct2: 0x006BFF45
*/
static uint8_t staff_mechanic_direction_surface(Peep* peep)
{
uint8_t direction = scenario_rand() & 3;
auto ride = get_ride(peep->CurrentRide);
if (ride != nullptr && (peep->State == PEEP_STATE_ANSWERING || peep->State == PEEP_STATE_HEADING_TO_INSPECTION)
&& (scenario_rand() & 1))
{
auto location = ride_get_exit_location(ride, peep->CurrentRideStation);
if (location.isNull())
{
location = ride_get_entrance_location(ride, peep->CurrentRideStation);
}
CoordsXY chosenTile = location.ToCoordsXY();
int16_t x_diff = chosenTile.x - peep->x;
int16_t y_diff = chosenTile.y - peep->y;
if (abs(x_diff) <= abs(y_diff))
{
direction = y_diff < 0 ? 3 : 1;
}
else
{
direction = x_diff < 0 ? 0 : 2;
}
}
return staff_direction_surface(peep, direction);
}
/**
*
* rct2: 0x006C02D1
*/
static uint8_t staff_mechanic_direction_path_rand(Peep* peep, uint8_t pathDirections)
{
if (scenario_rand() & 1)
{
if (pathDirections & (1 << peep->PeepDirection))
return peep->PeepDirection;
}
// Modified from original to spam scenario_rand less
uint8_t direction = scenario_rand() & 3;
for (int32_t i = 0; i < 4; ++i, ++direction)
{
direction &= 3;
if (pathDirections & (1 << direction))
return direction;
}
// This will never happen as pathDirections always has a bit set.
return peep->PeepDirection;
}
/**
*
* rct2: 0x006C0121
*/
static uint8_t staff_mechanic_direction_path(Peep* peep, uint8_t validDirections, PathElement* pathElement)
{
Direction direction = INVALID_DIRECTION;
uint8_t pathDirections = pathElement->GetEdges();
pathDirections &= validDirections;
if (pathDirections == 0)
{
return staff_mechanic_direction_surface(peep);
}
// Check if this is dead end - i.e. only way out is the reverse direction.
pathDirections &= ~(1 << direction_reverse(peep->PeepDirection));
if (pathDirections == 0)
{
pathDirections |= (1 << direction_reverse(peep->PeepDirection));
}
direction = bitscanforward(pathDirections);
pathDirections &= ~(1 << direction);
if (pathDirections == 0)
{
if (peep->State != PEEP_STATE_ANSWERING && peep->State != PEEP_STATE_HEADING_TO_INSPECTION)
{
return direction;
}
if (peep->SubState != 2)
{
return direction;
}
peep->SubState = 3;
}
pathDirections |= (1 << direction);
// Mechanic is heading to ride (either broken down or for inspection).
auto ride = get_ride(peep->CurrentRide);
if (ride != nullptr && (peep->State == PEEP_STATE_ANSWERING || peep->State == PEEP_STATE_HEADING_TO_INSPECTION))
{
/* Find location of the exit for the target ride station
* or if the ride has no exit, the entrance. */
TileCoordsXYZD location = ride_get_exit_location(ride, peep->CurrentRideStation);
if (location.isNull())
{
location = ride_get_entrance_location(ride, peep->CurrentRideStation);
// If no entrance is present either. This is an incorrect state.
if (location.isNull())
{
return staff_mechanic_direction_path_rand(peep, pathDirections);
}
}
gPeepPathFindGoalPosition.x = location.x;
gPeepPathFindGoalPosition.y = location.y;
gPeepPathFindGoalPosition.z = location.z;
gPeepPathFindIgnoreForeignQueues = false;
gPeepPathFindQueueRideIndex = RIDE_ID_NULL;
#if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
pathfind_logging_enable(peep);
#endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
Direction pathfindDirection = peep_pathfind_choose_direction(TileCoordsXYZ{ peep->NextLoc }, peep);
#if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
pathfind_logging_disable();
#endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
if (pathfindDirection == INVALID_DIRECTION)
{
/* Heuristic search failed for all directions.
* Reset the PathfindGoal - this means that the PathfindHistory
* will be reset in the next call to peep_pathfind_choose_direction().
* 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). */
peep_reset_pathfind_goal(peep);
return staff_mechanic_direction_path_rand(peep, pathDirections);
}
return static_cast<uint8_t>(pathfindDirection);
}
return staff_mechanic_direction_path_rand(peep, pathDirections);
}
/**
*
* rct2: 0x006BFF2C
*/
bool Staff::DoMechanicPathFinding()
{
uint8_t validDirections = staff_get_valid_patrol_directions(this, NextLoc);
Direction newDirection = INVALID_DIRECTION;
if (GetNextIsSurface())
{
newDirection = staff_mechanic_direction_surface(this);
}
else
{
auto* pathElement = map_get_path_element_at(TileCoordsXYZ{ NextLoc });
if (pathElement == nullptr)
return true;
newDirection = staff_mechanic_direction_path(this, validDirections, pathElement);
}
// countof(CoordsDirectionDelta)
assert(newDirection < 8);
CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
while (!map_is_location_valid(chosenTile))
{
newDirection = staff_mechanic_direction_surface(this);
chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
}
PeepDirection = newDirection;
DestinationX = chosenTile.x + 16;
DestinationY = chosenTile.y + 16;
DestinationTolerance = (scenario_rand() & 7) + 2;
return false;
}
/**
*
* rct2: 0x006C050B
*/
static uint8_t staff_direction_path(Peep* peep, uint8_t validDirections, PathElement* pathElement)
{
Direction direction = INVALID_DIRECTION;
uint8_t pathDirections = pathElement->GetEdges();
if (peep->State != PEEP_STATE_ANSWERING && peep->State != PEEP_STATE_HEADING_TO_INSPECTION)
{
pathDirections &= validDirections;
}
if (pathDirections == 0)
{
return staff_direction_surface(peep, scenario_rand() & 3);
}
pathDirections &= ~(1 << direction_reverse(peep->PeepDirection));
if (pathDirections == 0)
{
pathDirections |= (1 << direction_reverse(peep->PeepDirection));
}
direction = bitscanforward(pathDirections);
pathDirections &= ~(1 << direction);
if (pathDirections == 0)
{
return direction;
}
pathDirections |= (1 << direction);
direction = scenario_rand() & 3;
for (int32_t i = 0; i < 4; ++i, ++direction)
{
direction &= 3;
if (pathDirections & (1 << direction))
return direction;
}
// This will never happen as pathDirections will always have a bit set
return direction;
}
/**
*
* rct2: 0x006C0351
*/
bool Staff::DoMiscPathFinding()
{
uint8_t validDirections = staff_get_valid_patrol_directions(this, NextLoc);
Direction newDirection = INVALID_DIRECTION;
if (GetNextIsSurface())
{
newDirection = staff_direction_surface(this, scenario_rand() & 3);
}
else
{
auto* pathElement = map_get_path_element_at(TileCoordsXYZ{ NextLoc });
if (pathElement == nullptr)
return true;
newDirection = staff_direction_path(this, validDirections, pathElement);
}
CoordsXY chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
while (!map_is_location_valid(chosenTile))
{
newDirection = staff_direction_surface(this, scenario_rand() & 3);
chosenTile = CoordsXY{ NextLoc } + CoordsDirectionDelta[newDirection];
}
PeepDirection = newDirection;
DestinationX = chosenTile.x + 16;
DestinationY = chosenTile.y + 16;
DestinationTolerance = (scenario_rand() & 7) + 2;
return false;
}
/**
*
* rct2: 0x006C086D
*/
static void staff_entertainer_update_nearby_peeps(Peep* peep)
{
for (auto guest : EntityList<Guest>(EntityListId::Peep))
{
if (guest->x == LOCATION_NULL)
continue;
int16_t z_dist = abs(peep->z - guest->z);
if (z_dist > 48)
continue;
int16_t x_dist = abs(peep->x - guest->x);
int16_t y_dist = abs(peep->y - guest->y);
if (x_dist > 96)
continue;
if (y_dist > 96)
continue;
if (peep->State == PEEP_STATE_WALKING)
{
peep->HappinessTarget = std::min(peep->HappinessTarget + 4, PEEP_MAX_HAPPINESS);
}
else if (peep->State == PEEP_STATE_QUEUING)
{
if (peep->TimeInQueue > 200)
{
peep->TimeInQueue -= 200;
}
else
{
peep->TimeInQueue = 0;
}
peep->HappinessTarget = std::min(peep->HappinessTarget + 3, PEEP_MAX_HAPPINESS);
}
}
}
/**
*
* rct2: 0x006C05AE
*/
bool Staff::DoEntertainerPathFinding()
{
if (((scenario_rand() & 0xFFFF) <= 0x4000) && (Action == PEEP_ACTION_NONE_1 || Action == PEEP_ACTION_NONE_2))
{
Action = (scenario_rand() & 1) ? PEEP_ACTION_WAVE_2 : PEEP_ACTION_JOY;
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
staff_entertainer_update_nearby_peeps(this);
}
return DoMiscPathFinding();
}
/**
*
* rct2: 0x006BF926
*/
bool Staff::DoPathFinding()
{
switch (StaffType)
{
case STAFF_TYPE_HANDYMAN:
return DoHandymanPathFinding();
case STAFF_TYPE_MECHANIC:
return DoMechanicPathFinding();
case STAFF_TYPE_SECURITY:
return DoMiscPathFinding();
case STAFF_TYPE_ENTERTAINER:
return DoEntertainerPathFinding();
default:
assert(false);
return 0;
}
}
uint8_t Staff::GetCostume() const
{
return SpriteType - PEEP_SPRITE_TYPE_ENTERTAINER_PANDA;
}
void Staff::SetCostume(uint8_t value)
{
SpriteType = static_cast<PeepSpriteType>(value + PEEP_SPRITE_TYPE_ENTERTAINER_PANDA);
}
colour_t staff_get_colour(uint8_t staffType)
{
switch (staffType)
{
case STAFF_TYPE_HANDYMAN:
return gStaffHandymanColour;
case STAFF_TYPE_MECHANIC:
return gStaffMechanicColour;
case STAFF_TYPE_SECURITY:
return gStaffSecurityColour;
case STAFF_TYPE_ENTERTAINER:
return 0;
default:
assert(false);
return 0;
}
}
bool staff_set_colour(uint8_t staffType, colour_t value)
{
switch (staffType)
{
case STAFF_TYPE_HANDYMAN:
gStaffHandymanColour = value;
break;
case STAFF_TYPE_MECHANIC:
gStaffMechanicColour = value;
break;
case STAFF_TYPE_SECURITY:
gStaffSecurityColour = value;
break;
default:
return false;
}
return true;
}
uint32_t staff_get_available_entertainer_costumes()
{
uint32_t entertainerCostumes = 0;
for (int32_t i = 0; i < MAX_SCENERY_GROUP_OBJECTS; i++)
{
if (scenery_group_is_invented(i))
{
const auto sgEntry = get_scenery_group_entry(i);
entertainerCostumes |= sgEntry->entertainer_costumes;
}
}
// For some reason the flags are +4 from the actual costume IDs
entertainerCostumes >>= 4;
// Fix #6593: force enable the default costumes, which normally get enabled through the default scenery groups.
entertainerCostumes |= (1 << ENTERTAINER_COSTUME_PANDA) | (1 << ENTERTAINER_COSTUME_TIGER)
| (1 << ENTERTAINER_COSTUME_ELEPHANT);
return entertainerCostumes;
}
int32_t staff_get_available_entertainer_costume_list(uint8_t* costumeList)
{
uint32_t availableCostumes = staff_get_available_entertainer_costumes();
int32_t numCostumes = 0;
for (uint8_t i = 0; i < ENTERTAINER_COSTUME_COUNT; i++)
{
if (availableCostumes & (1 << i))
{
costumeList[numCostumes++] = i;
}
}
return numCostumes;
}
/** rct2: 0x009929C8 */
static constexpr const CoordsXY _MowingWaypoints[] = {
{ 28, 28 }, { 28, 4 }, { 20, 4 }, { 20, 28 }, { 12, 28 }, { 12, 4 }, { 4, 4 }, { 4, 28 },
};
/**
*
* rct2: 0x006BF567
*/
void Staff::UpdateMowing()
{
if (!CheckForPath())
return;
while (true)
{
if (auto loc = UpdateAction())
{
int16_t checkZ = tile_element_height(*loc);
MoveTo({ *loc, checkZ });
return;
}
Var37++;
if (Var37 == 1)
{
SwitchToSpecialSprite(2);
}
if (Var37 == std::size(_MowingWaypoints))
{
StateReset();
return;
}
DestinationX = _MowingWaypoints[Var37].x + NextLoc.x;
DestinationY = _MowingWaypoints[Var37].y + NextLoc.y;
if (Var37 != 7)
continue;
auto surfaceElement = map_get_surface_element_at(NextLoc);
if (surfaceElement != nullptr && surfaceElement->CanGrassGrow())
{
surfaceElement->SetGrassLength(GRASS_LENGTH_MOWED);
map_invalidate_tile_zoom0({ NextLoc, surfaceElement->GetBaseZ(), surfaceElement->GetBaseZ() + 16 });
}
StaffLawnsMown++;
WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
}
}
/**
*
* rct2: 0x006BF7E6
*/
void Staff::UpdateWatering()
{
StaffMowingTimeout = 0;
if (SubState == 0)
{
if (!CheckForPath())
return;
uint8_t pathingResult;
PerformNextAction(pathingResult);
if (!(pathingResult & PATHING_DESTINATION_REACHED))
return;
sprite_direction = (Var37 & 3) << 3;
Action = PEEP_ACTION_STAFF_WATERING;
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
SubState = 1;
}
else if (SubState == 1)
{
if (Action != PEEP_ACTION_NONE_2)
{
UpdateAction();
Invalidate();
return;
}
auto actionLoc = CoordsXY{ NextLoc } + CoordsDirectionDelta[Var37];
TileElement* tile_element = map_get_first_element_at(actionLoc);
if (tile_element == nullptr)
return;
do
{
if (tile_element->GetType() != TILE_ELEMENT_TYPE_SMALL_SCENERY)
continue;
if (abs(NextLoc.z - tile_element->GetBaseZ()) > 4 * COORDS_Z_STEP)
continue;
rct_scenery_entry* scenery_entry = tile_element->AsSmallScenery()->GetEntry();
if (!scenery_small_entry_has_flag(scenery_entry, SMALL_SCENERY_FLAG_CAN_BE_WATERED))
continue;
tile_element->AsSmallScenery()->SetAge(0);
map_invalidate_tile_zoom0({ actionLoc, tile_element->GetBaseZ(), tile_element->GetClearanceZ() });
StaffGardensWatered++;
WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
} while (!(tile_element++)->IsLastForTile());
StateReset();
}
}
/**
*
* rct2: 0x006BF6C9
*/
void Staff::UpdateEmptyingBin()
{
StaffMowingTimeout = 0;
if (SubState == 0)
{
if (!CheckForPath())
return;
uint8_t pathingResult;
PerformNextAction(pathingResult);
if (!(pathingResult & PATHING_DESTINATION_REACHED))
return;
sprite_direction = (Var37 & 3) << 3;
Action = PEEP_ACTION_STAFF_EMPTY_BIN;
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
SubState = 1;
}
else if (SubState == 1)
{
if (Action == PEEP_ACTION_NONE_2)
{
StateReset();
return;
}
UpdateAction();
Invalidate();
if (ActionFrame != 11)
return;
TileElement* tile_element = map_get_first_element_at(NextLoc);
if (tile_element == nullptr)
return;
for (;; tile_element++)
{
if (tile_element->GetType() == TILE_ELEMENT_TYPE_PATH)
{
if (NextLoc.z == tile_element->GetBaseZ())
break;
}
if ((tile_element)->IsLastForTile())
{
StateReset();
return;
}
}
if (!tile_element->AsPath()->HasAddition())
{
StateReset();
return;
}
rct_scenery_entry* scenery_entry = tile_element->AsPath()->GetAdditionEntry();
if (!(scenery_entry->path_bit.flags & PATH_BIT_FLAG_IS_BIN) || tile_element->AsPath()->IsBroken()
|| tile_element->AsPath()->AdditionIsGhost())
{
StateReset();
return;
}
uint8_t additionStatus = tile_element->AsPath()->GetAdditionStatus() | ((3 << Var37) << Var37);
tile_element->AsPath()->SetAdditionStatus(additionStatus);
map_invalidate_tile_zoom0({ NextLoc, tile_element->GetBaseZ(), tile_element->GetClearanceZ() });
StaffBinsEmptied++;
WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
}
}
/**
*
* rct2: 0x6BF641
*/
void Staff::UpdateSweeping()
{
StaffMowingTimeout = 0;
if (!CheckForPath())
return;
if (Action == PEEP_ACTION_STAFF_SWEEP && ActionFrame == 8)
{
// Remove sick at this location
litter_remove_at({ x, y, z });
StaffLitterSwept++;
WindowInvalidateFlags |= PEEP_INVALIDATE_STAFF_STATS;
}
if (auto loc = UpdateAction())
{
int16_t actionZ = GetZOnSlope((*loc).x, (*loc).y);
MoveTo({ *loc, actionZ });
return;
}
Var37++;
if (Var37 != 2)
{
Action = PEEP_ACTION_STAFF_SWEEP;
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
return;
}
StateReset();
}
/**
*
* rct2: 0x006C16D7
*/
void Staff::UpdateHeadingToInspect()
{
auto ride = get_ride(CurrentRide);
if (ride == nullptr)
{
SetState(PEEP_STATE_FALLING);
return;
}
if (ride_get_exit_location(ride, CurrentRideStation).isNull())
{
ride->lifecycle_flags &= ~RIDE_LIFECYCLE_DUE_INSPECTION;
SetState(PEEP_STATE_FALLING);
return;
}
if (ride->mechanic_status != RIDE_MECHANIC_STATUS_HEADING || !(ride->lifecycle_flags & RIDE_LIFECYCLE_DUE_INSPECTION))
{
SetState(PEEP_STATE_FALLING);
return;
}
if (SubState == 0)
{
MechanicTimeSinceCall = 0;
peep_reset_pathfind_goal(this);
SubState = 2;
}
if (SubState <= 3)
{
MechanicTimeSinceCall++;
if (MechanicTimeSinceCall > 2500)
{
if (ride->lifecycle_flags & RIDE_LIFECYCLE_DUE_INSPECTION && ride->mechanic_status == RIDE_MECHANIC_STATUS_HEADING)
{
ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
}
SetState(PEEP_STATE_FALLING);
return;
}
if (!CheckForPath())
return;
uint8_t pathingResult;
TileElement* rideEntranceExitElement;
PerformNextAction(pathingResult, rideEntranceExitElement);
if (!(pathingResult & PATHING_RIDE_EXIT) && !(pathingResult & PATHING_RIDE_ENTRANCE))
{
return;
}
if (CurrentRide != rideEntranceExitElement->AsEntrance()->GetRideIndex())
return;
uint8_t exit_index = rideEntranceExitElement->AsEntrance()->GetStationIndex();
if (CurrentRideStation != exit_index)
return;
if (pathingResult & PATHING_RIDE_ENTRANCE)
{
if (!ride_get_exit_location(ride, exit_index).isNull())
{
return;
}
}
PeepDirection = rideEntranceExitElement->GetDirection();
int32_t destX = NextLoc.x + 16 + DirectionOffsets[PeepDirection].x * 53;
int32_t destY = NextLoc.y + 16 + DirectionOffsets[PeepDirection].y * 53;
DestinationX = destX;
DestinationY = destY;
DestinationTolerance = 2;
sprite_direction = PeepDirection << 3;
z = rideEntranceExitElement->base_height * 4;
SubState = 4;
// Falls through into SubState 4
}
int16_t delta_y = abs(y - DestinationY);
if (auto loc = UpdateAction())
{
int32_t newZ = ride->stations[CurrentRideStation].GetBaseZ();
if (delta_y < 20)
{
newZ += RideTypeDescriptors[ride->type].Heights.PlatformHeight;
}
MoveTo({ *loc, newZ });
return;
}
SetState(PEEP_STATE_INSPECTING);
SubState = 0;
}
/**
*
* rct2: 0x006C0CB8
*/
void Staff::UpdateAnswering()
{
auto ride = get_ride(CurrentRide);
if (ride == nullptr || ride->mechanic_status != RIDE_MECHANIC_STATUS_HEADING)
{
SetState(PEEP_STATE_FALLING);
return;
}
if (SubState == 0)
{
Action = PEEP_ACTION_STAFF_ANSWER_CALL;
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
SubState = 1;
peep_window_state_update(this);
return;
}
else if (SubState == 1)
{
if (Action == PEEP_ACTION_NONE_2)
{
SubState = 2;
peep_window_state_update(this);
MechanicTimeSinceCall = 0;
peep_reset_pathfind_goal(this);
return;
}
UpdateAction();
Invalidate();
return;
}
else if (SubState <= 3)
{
MechanicTimeSinceCall++;
if (MechanicTimeSinceCall > 2500)
{
ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING;
ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
SetState(PEEP_STATE_FALLING);
return;
}
if (!CheckForPath())
return;
uint8_t pathingResult;
TileElement* rideEntranceExitElement;
PerformNextAction(pathingResult, rideEntranceExitElement);
if (!(pathingResult & PATHING_RIDE_EXIT) && !(pathingResult & PATHING_RIDE_ENTRANCE))
{
return;
}
if (CurrentRide != rideEntranceExitElement->AsEntrance()->GetRideIndex())
return;
uint8_t exit_index = rideEntranceExitElement->AsEntrance()->GetStationIndex();
if (CurrentRideStation != exit_index)
return;
if (pathingResult & PATHING_RIDE_ENTRANCE)
{
if (!ride_get_exit_location(ride, exit_index).isNull())
{
return;
}
}
PeepDirection = rideEntranceExitElement->GetDirection();
int32_t destX = NextLoc.x + 16 + DirectionOffsets[PeepDirection].x * 53;
int32_t destY = NextLoc.y + 16 + DirectionOffsets[PeepDirection].y * 53;
DestinationX = destX;
DestinationY = destY;
DestinationTolerance = 2;
sprite_direction = PeepDirection << 3;
z = rideEntranceExitElement->base_height * 4;
SubState = 4;
// Falls through into SubState 4
}
int16_t delta_y = abs(y - DestinationY);
if (auto loc = UpdateAction())
{
int32_t newZ = ride->stations[CurrentRideStation].GetBaseZ();
if (delta_y < 20)
{
newZ += RideTypeDescriptors[ride->type].Heights.PlatformHeight;
}
MoveTo({ *loc, newZ });
return;
}
SetState(PEEP_STATE_FIXING);
SubState = 0;
}
/** rct2: 0x00992A5C */
static constexpr const CoordsXY _WateringUseOffsets[] = {
{ 3, 16 }, { 16, 29 }, { 29, 16 }, { 16, 3 }, { 3, 29 }, { 29, 29 }, { 29, 3 }, { 3, 3 },
};
/**
*
* rct2: 0x006BF483
*/
static int32_t peep_update_patrolling_find_watering(Peep* peep)
{
if (!(peep->StaffOrders & STAFF_ORDERS_WATER_FLOWERS))
return 0;
uint8_t chosen_position = scenario_rand() & 7;
for (int32_t i = 0; i < 8; ++i, ++chosen_position)
{
chosen_position &= 7;
auto chosenLoc = CoordsXY{ peep->NextLoc } + CoordsDirectionDelta[chosen_position];
TileElement* tile_element = map_get_first_element_at(chosenLoc);
// This seems to happen in some SV4 files.
if (tile_element == nullptr)
{
continue;
}
do
{
if (tile_element->GetType() != TILE_ELEMENT_TYPE_SMALL_SCENERY)
{
continue;
}
auto z_diff = abs(peep->NextLoc.z - tile_element->GetBaseZ());
if (z_diff >= 4 * COORDS_Z_STEP)
{
continue;
}
rct_scenery_entry* sceneryEntry = tile_element->AsSmallScenery()->GetEntry();
if (sceneryEntry == nullptr || !scenery_small_entry_has_flag(sceneryEntry, SMALL_SCENERY_FLAG_CAN_BE_WATERED))
{
continue;
}
if (tile_element->AsSmallScenery()->GetAge() < SCENERY_WITHER_AGE_THRESHOLD_2)
{
if (chosen_position >= 4)
{
continue;
}
if (tile_element->AsSmallScenery()->GetAge() < SCENERY_WITHER_AGE_THRESHOLD_1)
{
continue;
}
}
peep->SetState(PEEP_STATE_WATERING);
peep->Var37 = chosen_position;
peep->SubState = 0;
peep->DestinationX = (peep->x & 0xFFE0) + _WateringUseOffsets[chosen_position].x;
peep->DestinationY = (peep->y & 0xFFE0) + _WateringUseOffsets[chosen_position].y;
peep->DestinationTolerance = 3;
return 1;
} while (!(tile_element++)->IsLastForTile());
}
return 0;
}
/**
*
* rct2: 0x006BF3A1
*/
static int32_t peep_update_patrolling_find_bin(Peep* peep)
{
if (!(peep->StaffOrders & STAFF_ORDERS_EMPTY_BINS))
return 0;
if (peep->GetNextIsSurface())
return 0;
TileElement* tileElement = map_get_first_element_at(peep->NextLoc);
if (tileElement == nullptr)
return 0;
for (;; tileElement++)
{
if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH && (tileElement->GetBaseZ() == peep->NextLoc.z))
break;
if (tileElement->IsLastForTile())
return 0;
}
if (!tileElement->AsPath()->HasAddition())
return 0;
rct_scenery_entry* sceneryEntry = tileElement->AsPath()->GetAdditionEntry();
if (sceneryEntry == nullptr)
return 0;
if (!(sceneryEntry->path_bit.flags & PATH_BIT_FLAG_IS_BIN))
return 0;
if (tileElement->AsPath()->IsBroken())
return 0;
if (tileElement->AsPath()->AdditionIsGhost())
return 0;
uint8_t bin_positions = tileElement->AsPath()->GetEdges();
uint8_t bin_quantity = tileElement->AsPath()->GetAdditionStatus();
uint8_t chosen_position = 0;
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 0;
peep->Var37 = chosen_position;
peep->SetState(PEEP_STATE_EMPTYING_BIN);
peep->SubState = 0;
peep->DestinationX = (peep->x & 0xFFE0) + BinUseOffsets[chosen_position].x;
peep->DestinationY = (peep->y & 0xFFE0) + BinUseOffsets[chosen_position].y;
peep->DestinationTolerance = 3;
return 1;
}
/**
*
* rct2: 0x006BF322
*/
static int32_t peep_update_patrolling_find_grass(Peep* peep)
{
if (!(peep->StaffOrders & STAFF_ORDERS_MOWING))
return 0;
if (peep->StaffMowingTimeout < 12)
return 0;
if (!(peep->GetNextIsSurface()))
return 0;
auto surfaceElement = map_get_surface_element_at(peep->NextLoc);
if (surfaceElement != nullptr && surfaceElement->CanGrassGrow())
{
if ((surfaceElement->GetGrassLength() & 0x7) >= GRASS_LENGTH_CLEAR_1)
{
peep->SetState(PEEP_STATE_MOWING);
peep->Var37 = 0;
// Original code used .y for both x and y. Changed to .x to make more sense (both x and y are 28)
peep->DestinationX = peep->NextLoc.x + _MowingWaypoints[0].x;
peep->DestinationY = peep->NextLoc.y + _MowingWaypoints[0].y;
peep->DestinationTolerance = 3;
return 1;
}
}
return 0;
}
/**
*
* rct2: 0x006BF295
*/
static int32_t peep_update_patrolling_find_sweeping(Peep* peep)
{
if (!(peep->StaffOrders & STAFF_ORDERS_SWEEPING))
return 0;
auto quad = EntityTileList<Litter>({ peep->x, peep->y });
for (auto litter : quad)
{
uint16_t z_diff = abs(peep->z - litter->z);
if (z_diff >= 16)
continue;
peep->SetState(PEEP_STATE_SWEEPING);
peep->Var37 = 0;
peep->DestinationX = litter->x;
peep->DestinationY = litter->y;
peep->DestinationTolerance = 5;
return 1;
}
return 0;
}
void Staff::Tick128UpdateStaff()
{
if (StaffType != STAFF_TYPE_SECURITY)
return;
PeepSpriteType newSpriteType = PEEP_SPRITE_TYPE_SECURITY_ALT;
if (State != PEEP_STATE_PATROLLING)
newSpriteType = PEEP_SPRITE_TYPE_SECURITY;
if (SpriteType == newSpriteType)
return;
SpriteType = newSpriteType;
ActionSpriteImageOffset = 0;
WalkingFrameNum = 0;
if (Action < PEEP_ACTION_NONE_1)
Action = PEEP_ACTION_NONE_2;
PeepFlags &= ~PEEP_FLAGS_SLOW_WALK;
if (gSpriteTypeToSlowWalkMap[newSpriteType])
{
PeepFlags |= PEEP_FLAGS_SLOW_WALK;
}
ActionSpriteType = PEEP_ACTION_SPRITE_TYPE_INVALID;
UpdateCurrentActionSpriteType();
}
bool Staff::IsMechanic() const
{
return (
sprite_identifier == SPRITE_IDENTIFIER_PEEP && AssignedPeepType == PEEP_TYPE_STAFF && StaffType == STAFF_TYPE_MECHANIC);
}
void Staff::UpdateStaff(uint32_t stepsToTake)
{
switch (State)
{
case PEEP_STATE_PATROLLING:
UpdatePatrolling();
break;
case PEEP_STATE_MOWING:
UpdateMowing();
break;
case PEEP_STATE_SWEEPING:
UpdateSweeping();
break;
case PEEP_STATE_ANSWERING:
UpdateAnswering();
break;
case PEEP_STATE_FIXING:
UpdateFixing(stepsToTake);
break;
case PEEP_STATE_INSPECTING:
UpdateFixing(stepsToTake);
break;
case PEEP_STATE_EMPTYING_BIN:
UpdateEmptyingBin();
break;
case PEEP_STATE_WATERING:
UpdateWatering();
break;
case PEEP_STATE_HEADING_TO_INSPECTION:
UpdateHeadingToInspect();
break;
default:
// TODO reset to default state
assert(false);
break;
}
}
/**
*
* rct2: 0x006BF1FD
*/
void Staff::UpdatePatrolling()
{
if (!CheckForPath())
return;
uint8_t pathingResult;
PerformNextAction(pathingResult);
if (!(pathingResult & PATHING_DESTINATION_REACHED))
return;
if (GetNextIsSurface())
{
auto surfaceElement = map_get_surface_element_at(NextLoc);
if (surfaceElement != nullptr)
{
int32_t water_height = surfaceElement->GetWaterHeight();
if (water_height > 0)
{
MoveTo({ x, y, water_height });
SetState(PEEP_STATE_FALLING);
return;
}
}
}
if (StaffType != STAFF_TYPE_HANDYMAN)
return;
if (peep_update_patrolling_find_sweeping(this))
return;
if (peep_update_patrolling_find_grass(this))
return;
if (peep_update_patrolling_find_bin(this))
return;
peep_update_patrolling_find_watering(this);
}
enum
{
PEEP_FIXING_ENTER_STATION = 0,
PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE = 1,
PEEP_FIXING_FIX_VEHICLE_CLOSED_RESTRAINTS = 2,
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 const uint32_t FixingSubstatesForBreakdown[9] = {
( // 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)
),
( // 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)
),
( // 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)
),
( // 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)
),
( // 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)
),
( // 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)
),
( // 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)
),
( // 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)
),
( // 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.
*/
void Staff::UpdateFixing(int32_t steps)
{
auto ride = get_ride(CurrentRide);
if (ride == nullptr)
{
SetState(PEEP_STATE_FALLING);
return;
}
bool progressToNextSubstate = true;
bool firstRun = true;
if ((State == PEEP_STATE_INSPECTING)
&& (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 = PEEP_STATE_FIXING;
}
while (progressToNextSubstate)
{
switch (SubState)
{
case PEEP_FIXING_ENTER_STATION:
NextFlags &= ~PEEP_NEXT_FLAG_IS_SLOPED;
progressToNextSubstate = UpdateFixingEnterStation(ride);
break;
case PEEP_FIXING_MOVE_TO_BROKEN_DOWN_VEHICLE:
progressToNextSubstate = UpdateFixingMoveToBrokenDownVehicle(firstRun, ride);
break;
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);
break;
case PEEP_FIXING_FIX_VEHICLE_MALFUNCTION:
progressToNextSubstate = UpdateFixingFixVehicleMalfunction(firstRun, ride);
break;
case PEEP_FIXING_MOVE_TO_STATION_END:
progressToNextSubstate = UpdateFixingMoveToStationEnd(firstRun, ride);
break;
case PEEP_FIXING_FIX_STATION_END:
progressToNextSubstate = UpdateFixingFixStationEnd(firstRun);
break;
case PEEP_FIXING_MOVE_TO_STATION_START:
progressToNextSubstate = UpdateFixingMoveToStationStart(firstRun, ride);
break;
case PEEP_FIXING_FIX_STATION_START:
progressToNextSubstate = UpdateFixingFixStationStart(firstRun, ride);
break;
case PEEP_FIXING_FIX_STATION_BRAKES:
progressToNextSubstate = UpdateFixingFixStationBrakes(firstRun, ride);
break;
case PEEP_FIXING_MOVE_TO_STATION_EXIT:
progressToNextSubstate = UpdateFixingMoveToStationExit(firstRun, ride);
break;
case PEEP_FIXING_FINISH_FIX_OR_INSPECT:
progressToNextSubstate = UpdateFixingFinishFixOrInspect(firstRun, steps, ride);
break;
case PEEP_FIXING_LEAVE_BY_ENTRANCE_EXIT:
progressToNextSubstate = UpdateFixingLeaveByEntranceExit(firstRun, ride);
break;
default:
log_error("Invalid substate");
progressToNextSubstate = false;
}
firstRun = false;
if (!progressToNextSubstate)
{
break;
}
int32_t subState = SubState;
uint32_t sub_state_sequence_mask = FixingSubstatesForBreakdown[8];
if (State != PEEP_STATE_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)
{
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, Ride* ride)
{
if (!firstRun)
{
Vehicle* vehicle = ride_get_broken_vehicle(ride);
if (vehicle == nullptr)
{
return true;
}
while (true)
{
if (vehicle->IsHead())
{
break;
}
uint8_t trackType = vehicle->GetTrackType();
if (track_type_is_station(trackType))
{
break;
}
vehicle = GET_VEHICLE(vehicle->prev_vehicle_on_ride);
}
CoordsXY offset = DirectionOffsets[PeepDirection];
DestinationX = (offset.x * -12) + vehicle->x;
DestinationY = (offset.y * -12) + vehicle->y;
DestinationTolerance = 2;
}
if (auto loc = UpdateAction())
{
MoveTo({ *loc, 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, Ride* ride)
{
if (!firstRun)
{
sprite_direction = PeepDirection << 3;
Action = (scenario_rand() & 1) ? PEEP_ACTION_STAFF_FIX_2 : PEEP_ACTION_STAFF_FIX;
ActionSpriteImageOffset = 0;
ActionFrame = 0;
UpdateCurrentActionSpriteType();
}
if (Action == PEEP_ACTION_NONE_2)
{
return true;
}
UpdateAction();
Invalidate();
uint8_t actionFrame = (Action == PEEP_ACTION_STAFF_FIX) ? 0x25 : 0x50;
if (ActionFrame != actionFrame)
{
return false;
}
Vehicle* vehicle = ride_get_broken_vehicle(ride);
if (vehicle == nullptr)
{
return true;
}
vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_CAR);
return false;
}
/**
* rct2: 0x006C107B
* fixing SubState: fix_vehicle_malfunction - applies fixing to vehicle malfunction.
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingFixVehicleMalfunction(bool firstRun, Ride* ride)
{
if (!firstRun)
{
sprite_direction = PeepDirection << 3;
Action = PEEP_ACTION_STAFF_FIX_3;
ActionSpriteImageOffset = 0;
ActionFrame = 0;
UpdateCurrentActionSpriteType();
}
if (Action == PEEP_ACTION_NONE_2)
{
return true;
}
UpdateAction();
Invalidate();
if (ActionFrame != 0x65)
{
return false;
}
Vehicle* vehicle = ride_get_broken_vehicle(ride);
if (vehicle == nullptr)
{
return true;
}
vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_TRAIN);
return false;
}
/** rct2: 0x00992A3C */
static constexpr const 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,
* inspection.
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingMoveToStationEnd(bool firstRun, Ride* ride)
{
if (!firstRun)
{
if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION | RIDE_TYPE_FLAG_HAS_NO_TRACK))
{
return true;
}
auto stationPos = ride->stations[CurrentRideStation].GetStart();
if (stationPos.isNull())
{
return true;
}
auto tileElement = map_get_track_element_at(stationPos);
if (tileElement == nullptr)
{
log_error("Couldn't find tile_element");
return false;
}
int32_t trackDirection = tileElement->GetDirection();
CoordsXY offset = _StationFixingOffsets[trackDirection];
stationPos.x += 16 + offset.x;
if (offset.x == 0)
{
stationPos.x = DestinationX;
}
stationPos.y += 16 + offset.y;
if (offset.y == 0)
{
stationPos.y = DestinationY;
}
DestinationX = stationPos.x;
DestinationY = stationPos.y;
DestinationTolerance = 2;
}
if (auto loc = UpdateAction())
{
MoveTo({ *loc, z });
return false;
}
return true;
}
/**
* rct2: 0x006C11F5
* fixing SubState: fix_station_end - applies to fixing station specific breakdowns: safety cut-out, control failure,
* inspection.
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingFixStationEnd(bool firstRun)
{
if (!firstRun)
{
sprite_direction = PeepDirection << 3;
Action = PEEP_ACTION_STAFF_CHECKBOARD;
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
}
if (Action == PEEP_ACTION_NONE_2)
{
return true;
}
UpdateAction();
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, Ride* ride)
{
if (!firstRun)
{
if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION | RIDE_TYPE_FLAG_HAS_NO_TRACK))
{
return true;
}
auto stationPosition = ride->stations[CurrentRideStation].GetStart();
if (stationPosition.isNull())
{
return true;
}
CoordsXYE input;
input.x = stationPosition.x;
input.y = stationPosition.y;
input.element = map_get_track_element_at_from_ride({ input.x, input.y, stationPosition.z }, CurrentRide);
if (input.element == nullptr)
{
return true;
}
Direction stationDirection = 0;
track_begin_end trackBeginEnd;
while (track_block_get_previous(input, &trackBeginEnd))
{
if (trackBeginEnd.begin_element->AsTrack()->IsStation())
{
input.x = trackBeginEnd.begin_x;
input.y = trackBeginEnd.begin_y;
input.element = trackBeginEnd.begin_element;
stationDirection = trackBeginEnd.begin_element->GetDirection();
continue;
}
break;
}
// loc_6C12ED:
uint16_t destinationX = input.x + 16;
uint16_t destinationY = input.y + 16;
CoordsXY offset = _StationFixingOffsets[stationDirection];
destinationX -= offset.x;
if (offset.x == 0)
{
destinationX = DestinationX;
}
destinationY -= offset.y;
if (offset.y == 0)
{
destinationY = DestinationY;
}
DestinationX = destinationX;
DestinationY = destinationY;
DestinationTolerance = 2;
}
if (auto loc = UpdateAction())
{
MoveTo({ *loc, 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, Ride* ride)
{
if (!firstRun)
{
if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_HAS_SINGLE_PIECE_STATION | RIDE_TYPE_FLAG_HAS_NO_TRACK))
{
return true;
}
sprite_direction = PeepDirection << 3;
Action = PEEP_ACTION_STAFF_FIX;
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
}
if (Action == PEEP_ACTION_NONE_2)
{
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)
{
sprite_direction = PeepDirection << 3;
Action = PEEP_ACTION_STAFF_FIX_GROUND;
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
}
if (Action == PEEP_ACTION_NONE_2)
{
return true;
}
UpdateAction();
Invalidate();
if (ActionFrame == 0x28)
{
ride->mechanic_status = RIDE_MECHANIC_STATUS_HAS_FIXED_STATION_BRAKES;
ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE;
}
if (ActionFrame == 0x13 || ActionFrame == 0x19 || ActionFrame == 0x1F || ActionFrame == 0x25 || ActionFrame == 0x2B)
{
audio_play_sound_at_location(SoundId::MechanicFix, { x, y, z });
}
return false;
}
/**
* rct2: 0x006C1474
* fixing SubState: move_to_station_exit - applies to fixing all failures & inspections
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingMoveToStationExit(bool firstRun, Ride* ride)
{
if (!firstRun)
{
auto stationPosition = ride_get_exit_location(ride, CurrentRideStation).ToCoordsXY();
if (stationPosition.isNull())
{
stationPosition = ride_get_entrance_location(ride, CurrentRideStation).ToCoordsXY();
if (stationPosition.isNull())
{
return true;
}
}
stationPosition = stationPosition.ToTileCentre();
CoordsXY stationPlatformDirection = DirectionOffsets[PeepDirection];
stationPosition.x += stationPlatformDirection.x * 20;
stationPosition.y += stationPlatformDirection.y * 20;
DestinationX = stationPosition.x;
DestinationY = stationPosition.y;
DestinationTolerance = 2;
}
if (auto loc = UpdateAction())
{
MoveTo({ *loc, z });
return false;
}
else
{
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)
{
ride->mechanic_status = RIDE_MECHANIC_STATUS_UNDEFINED;
if (State == PEEP_STATE_INSPECTING)
{
UpdateRideInspected(CurrentRide);
StaffRidesInspected++;
WindowInvalidateFlags |= RIDE_INVALIDATE_RIDE_INCOME | RIDE_INVALIDATE_RIDE_LIST;
return true;
}
StaffRidesFixed++;
WindowInvalidateFlags |= RIDE_INVALIDATE_RIDE_INCOME | RIDE_INVALIDATE_RIDE_LIST;
sprite_direction = PeepDirection << 3;
Action = PEEP_ACTION_STAFF_ANSWER_CALL_2;
ActionFrame = 0;
ActionSpriteImageOffset = 0;
UpdateCurrentActionSpriteType();
}
if (Action != PEEP_ACTION_NONE_2)
{
UpdateAction();
Invalidate();
return false;
}
ride_fix_breakdown(ride, steps);
return true;
}
/**
* rct2: 0x006C157E
* fixing SubState: leave_by_entrance_exit - applies to fixing all failures & inspections
* - see FixingSubstatesForBreakdown[]
*/
bool Staff::UpdateFixingLeaveByEntranceExit(bool firstRun, Ride* ride)
{
if (!firstRun)
{
auto exitPosition = ride_get_exit_location(ride, CurrentRideStation).ToCoordsXY();
if (exitPosition.isNull())
{
exitPosition = ride_get_entrance_location(ride, CurrentRideStation).ToCoordsXY();
if (exitPosition.isNull())
{
SetState(PEEP_STATE_FALLING);
return false;
}
}
exitPosition = exitPosition.ToTileCentre();
CoordsXY ebx_direction = DirectionOffsets[PeepDirection];
exitPosition.x -= ebx_direction.x * 19;
exitPosition.y -= ebx_direction.y * 19;
DestinationX = exitPosition.x;
DestinationY = exitPosition.y;
DestinationTolerance = 2;
}
int16_t xy_distance;
if (auto loc = UpdateAction(xy_distance))
{
uint16_t stationHeight = ride->stations[CurrentRideStation].GetBaseZ();
if (xy_distance >= 16)
{
stationHeight += RideTypeDescriptors[ride->type].Heights.PlatformHeight;
}
MoveTo({ *loc, stationHeight });
return false;
}
SetState(PEEP_STATE_FALLING);
return false;
}
/**
* rct2: 0x6B7588
*/
void Staff::UpdateRideInspected(ride_id_t rideIndex)
{
auto ride = get_ride(rideIndex);
if (ride != nullptr)
{
ride->lifecycle_flags &= ~RIDE_LIFECYCLE_DUE_INSPECTION;
ride->reliability += ((100 - ride->reliability_percentage) / 4) * (scenario_rand() & 0xFF);
ride->last_inspection = 0;
ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAINTENANCE | RIDE_INVALIDATE_RIDE_MAIN
| RIDE_INVALIDATE_RIDE_LIST;
}
}