#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
 * OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
 *
 * OpenRCT2 is the work of many authors, a full list can be found in contributors.md
 * For more information, visit https://github.com/OpenRCT2/OpenRCT2
 *
 * OpenRCT2 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * A full copy of the GNU General Public License can be found in licence.txt
 *****************************************************************************/
#pragma endregion

#include "../core/Math.hpp"
#include "../core/Util.hpp"
#include "../Context.h"
#include "../config/Config.h"
#include "../Game.h"
#include "../Input.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 "../ride/Station.h"
#include "../paint/tile_element/TileElement.h"
#include "../scenario/Scenario.h"
#include "../util/Util.h"
#include "../world/Entrance.h"
#include "../world/Footpath.h"
#include "../world/Scenery.h"
#include "../world/Sprite.h"
#include "Peep.h"
#include "Staff.h"

// 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 staff_id
// 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   gStaffPatrolAreas[(STAFF_MAX_COUNT + STAFF_TYPE_COUNT) * STAFF_PATROL_AREA_SIZE];
uint8    gStaffModes[STAFF_MAX_COUNT + STAFF_TYPE_COUNT];
uint16   gStaffDrawPatrolAreas;
colour_t gStaffHandymanColour;
colour_t gStaffMechanicColour;
colour_t gStaffSecurityColour;

/**
 *
 *  rct2: 0x006BD3A4
 */
void staff_reset_modes()
{
    for (sint32 i = 0; i < STAFF_MAX_COUNT; i++)
        gStaffModes[i] = STAFF_MODE_NONE;

    for (sint32 i = STAFF_MAX_COUNT; i < (STAFF_MAX_COUNT + STAFF_TYPE_COUNT); i++)
        gStaffModes[i] = STAFF_MODE_WALK;

    staff_update_greyed_patrol_areas();
}

static inline void staff_autoposition_new_staff_member(rct_peep * newPeep)
{
    // Find a location to place new staff member

    newPeep->state = PEEP_STATE_FALLING;

    sint16     x, y, z;
    uint32     count = 0;
    uint16     sprite_index;
    rct_peep * guest = nullptr;

    // Count number of walking guests
    FOR_ALL_GUESTS(sprite_index, guest)
    {
        if (guest->state == PEEP_STATE_WALKING)
            ++count;
    }

    if (count > 0)
    {
        // Place staff at a random guest
        uint32 rand = scenario_rand_max(count);
        FOR_ALL_GUESTS(sprite_index, guest)
        {
            if (guest->state == PEEP_STATE_WALKING)
            {
                if (rand == 0)
                    break;
                --rand;
            }
        }

        x = guest->x;
        y = guest->y;
        z = guest->z;
    }
    else
    {
        // No walking guests; pick random park entrance
        count = 0;
        uint8 i;
        for (i = 0; i < MAX_PARK_ENTRANCES; ++i)
        {
            if (gParkEntrances[i].x != LOCATION_NULL)
                ++count;
        }

        if (count > 0)
        {
            uint32 rand = scenario_rand_max(count);
            for (i = 0; i < MAX_PARK_ENTRANCES; ++i)
            {
                if (gParkEntrances[i].x != LOCATION_NULL)
                {
                    if (rand == 0)
                        break;
                    --rand;
                }
            }

            uint8 dir = gParkEntrances[i].direction;
            x         = gParkEntrances[i].x;
            y         = gParkEntrances[i].y;
            z         = gParkEntrances[i].z;
            x += 16 + ((dir & 1) == 0 ? ((dir & 2) ? 32 : -32) : 0);
            y += 16 + ((dir & 1) == 1 ? ((dir & 2) ? -32 : 32) : 0);
        }
        else
        {
            // No more options; user must pick a location
            newPeep->state = PEEP_STATE_PICKED;
            x              = newPeep->x;
            y              = newPeep->y;
            z              = newPeep->z;
        }
    }

    sprite_move(x, y, z + 16, (rct_sprite *)newPeep);
    invalidate_sprite_2((rct_sprite *)newPeep);
}

static money32 staff_hire_new_staff_member(uint8 staff_type, uint8 flags, sint16 command_x, sint16 command_y, sint16 command_z,
                                           sint32 autoposition, sint32 * newPeep_sprite_index)
{
    gCommandExpenditureType = RCT_EXPENDITURE_TYPE_WAGES;
    gCommandPosition.x      = command_x;
    gCommandPosition.y      = command_y;
    gCommandPosition.z      = command_z;

    if (gSpriteListCount[SPRITE_LIST_NULL] < 400)
    {
        gGameCommandErrorText = STR_TOO_MANY_PEOPLE_IN_GAME;
        return MONEY32_UNDEFINED;
    }

    // Staff type matches STAFF_TYPE enum, but ENTERTAINER onwards will match
    // the ENTERTAINER_COSTUME enum
    uint8 entertainerType = ENTERTAINER_COSTUME_PANDA;
    if (staff_type >= STAFF_TYPE_ENTERTAINER)
    {
        entertainerType = staff_type - STAFF_TYPE_ENTERTAINER;
        if (entertainerType >= ENTERTAINER_COSTUME_COUNT)
        {
            // Invalid entertainer costume
            return MONEY32_UNDEFINED;
        }

        uint32 availableCostumes = staff_get_available_entertainer_costumes();
        if (!(availableCostumes & (1 << entertainerType)))
        {
            // Entertainer costume unavailable
            return MONEY32_UNDEFINED;
        }

        staff_type = STAFF_TYPE_ENTERTAINER;
    }

    sint32 i;
    for (i = 0; i < STAFF_MAX_COUNT; ++i)
    {
        if (!(gStaffModes[i] & 1))
            break;
    }

    if (i == STAFF_MAX_COUNT)
    {
        gGameCommandErrorText = STR_TOO_MANY_STAFF_IN_GAME;
        return MONEY32_UNDEFINED;
    }

    if (flags & GAME_COMMAND_FLAG_APPLY)
    {
        sint32                    newStaffId = i;
        const rct_sprite_bounds * spriteBounds;
        rct_peep *                newPeep = &(create_sprite(flags)->peep);

        if (newPeep == nullptr)
        {
            gGameCommandErrorText = STR_TOO_MANY_PEOPLE_IN_GAME;
            return MONEY32_UNDEFINED;
        }

        if (flags == 0)
        {
            sprite_remove((rct_sprite *)newPeep);
        }
        else
        {
            move_sprite_to_list((rct_sprite *)newPeep, SPRITE_LIST_PEEP * 2);

            newPeep->sprite_identifier          = 1;
            newPeep->window_invalidate_flags    = 0;
            newPeep->action                     = PEEP_ACTION_NONE_2;
            newPeep->special_sprite             = 0;
            newPeep->action_sprite_image_offset = 0;
            newPeep->no_action_frame_no         = 0;
            newPeep->action_sprite_type         = 0;
            newPeep->var_C4                     = 0;
            newPeep->type                       = PEEP_TYPE_STAFF;
            newPeep->outside_of_park            = 0;
            newPeep->peep_flags                 = 0;
            newPeep->paid_to_enter              = 0;
            newPeep->paid_on_rides              = 0;
            newPeep->paid_on_food               = 0;
            newPeep->paid_on_souvenirs          = 0;

            if (staff_type == STAFF_TYPE_HANDYMAN)
                newPeep->staff_orders = STAFF_ORDERS_SWEEPING | STAFF_ORDERS_WATER_FLOWERS | STAFF_ORDERS_EMPTY_BINS;
            else if (staff_type == STAFF_TYPE_MECHANIC)
                newPeep->staff_orders = STAFF_ORDERS_INSPECT_RIDES | STAFF_ORDERS_FIX_RIDES;
            else
                newPeep->staff_orders = 0;

            uint16     idSearchSpriteIndex;
            rct_peep * idSearchPeep;

            // We search for the first available id for a given staff type
            uint32 newStaffIndex = 0;
            for (;;)
            {
                bool found = false;
                ++newStaffIndex;

                FOR_ALL_STAFF(idSearchSpriteIndex, idSearchPeep)
                {
                    if (idSearchPeep->staff_type != staff_type)
                        continue;

                    if (idSearchPeep->id == newStaffIndex)
                    {
                        found = true;
                        break;
                    }
                }

                if (!found)
                    break;
            }

            newPeep->id         = newStaffIndex;
            newPeep->staff_type = staff_type;

            static constexpr const rct_string_id staffNames[] = {
                STR_HANDYMAN_X,
                STR_MECHANIC_X,
                STR_SECURITY_GUARD_X,
                STR_ENTERTAINER_X,
            };

            /* rct2: 0x009929FC */
            static constexpr const uint8 spriteTypes[] = {
                PEEP_SPRITE_TYPE_HANDYMAN,
                PEEP_SPRITE_TYPE_MECHANIC,
                PEEP_SPRITE_TYPE_SECURITY,
                PEEP_SPRITE_TYPE_ENTERTAINER_PANDA,
            };

            uint8 sprite_type = spriteTypes[staff_type];
            if (staff_type == STAFF_TYPE_ENTERTAINER)
            {
                sprite_type = PEEP_SPRITE_TYPE_ENTERTAINER_PANDA + entertainerType;
            }
            newPeep->name_string_idx = staffNames[staff_type];
            newPeep->sprite_type     = sprite_type;

            spriteBounds                    = g_peep_animation_entries[sprite_type].sprite_bounds;
            newPeep->sprite_width           = spriteBounds->sprite_width;
            newPeep->sprite_height_negative = spriteBounds->sprite_height_negative;
            newPeep->sprite_height_positive = spriteBounds->sprite_height_positive;

            if (autoposition)
            {
                staff_autoposition_new_staff_member(newPeep);
            }
            else
            {
                newPeep->state = PEEP_STATE_PICKED;

                sprite_move(newPeep->x, newPeep->y, newPeep->z, (rct_sprite *)newPeep);
                invalidate_sprite_2((rct_sprite *)newPeep);
            }

            newPeep->time_in_park            = gDateMonthsElapsed;
            newPeep->pathfind_goal.x         = 0xFF;
            newPeep->pathfind_goal.y         = 0xFF;
            newPeep->pathfind_goal.z         = 0xFF;
            newPeep->pathfind_goal.direction = 0xFF;

            uint8 colour             = staff_get_colour(staff_type);
            newPeep->tshirt_colour   = colour;
            newPeep->trousers_colour = colour;

            // Staff energy determines their walking speed
            newPeep->energy        = 0x60;
            newPeep->energy_target = 0x60;
            newPeep->var_E2        = 0;

            peep_update_name_sort(newPeep);

            newPeep->staff_id = newStaffId;

            gStaffModes[newStaffId] = STAFF_MODE_WALK;

            for (i = 0; i < STAFF_PATROL_AREA_SIZE; i++)
            {
                gStaffPatrolAreas[newStaffId * STAFF_PATROL_AREA_SIZE + i] = 0;
            }
        }

        *newPeep_sprite_index = newPeep->sprite_index;
    }
    return 0;
}

/**
 *
 *  rct2: 0x006BEFA1
 */
void game_command_hire_new_staff_member(sint32 * eax, sint32 * ebx, sint32 * ecx, sint32 * edx, sint32 * esi, sint32 * edi,
                                        sint32 * ebp)
{
    *ebx = staff_hire_new_staff_member((*ebx & 0xFF00) >> 8, *ebx & 0xFF, *eax & 0xFFFF, *ecx & 0xFFFF, *edx & 0xFFFF,
                                       (*ebx & 0xFF0000) >> 16, edi);
}

/** rct2: 0x00982134 */
static constexpr const bool peep_slow_walking_types[] = {
    false, // PEEP_SPRITE_TYPE_NORMAL
    false, // PEEP_SPRITE_TYPE_HANDYMAN
    false, // PEEP_SPRITE_TYPE_MECHANIC
    false, // PEEP_SPRITE_TYPE_SECURITY
    false, // PEEP_SPRITE_TYPE_ENTERTAINER_PANDA
    false, // PEEP_SPRITE_TYPE_ENTERTAINER_TIGER
    false, // PEEP_SPRITE_TYPE_ENTERTAINER_ELEPHANT
    false, // PEEP_SPRITE_TYPE_ENTERTAINER_ROMAN
    false, // PEEP_SPRITE_TYPE_ENTERTAINER_GORILLA
    false, // PEEP_SPRITE_TYPE_ENTERTAINER_SNOWMAN
    false, // PEEP_SPRITE_TYPE_ENTERTAINER_KNIGHT
    true,  // PEEP_SPRITE_TYPE_ENTERTAINER_ASTRONAUT
    false, // PEEP_SPRITE_TYPE_ENTERTAINER_BANDIT
    false, // PEEP_SPRITE_TYPE_ENTERTAINER_SHERIFF
    true,  // PEEP_SPRITE_TYPE_ENTERTAINER_PIRATE
    true,  // PEEP_SPRITE_TYPE_BALLOON
};

/**
 *
 *  rct2: 0x006C0BB5
 */
void game_command_set_staff_order(sint32 * eax, sint32 * ebx, sint32 * ecx, sint32 * edx, sint32 * esi, sint32 * edi,
                                  sint32 * ebp)
{
    gCommandExpenditureType = RCT_EXPENDITURE_TYPE_WAGES;
    uint8  order_id         = *ebx >> 8;
    uint16 sprite_id        = *edx;
    if (sprite_id >= MAX_SPRITES)
    {
        log_warning("Invalid game command, sprite_id = %u", sprite_id);
        *ebx = MONEY32_UNDEFINED;
        return;
    }
    if (*ebx & GAME_COMMAND_FLAG_APPLY)
    {
        rct_peep * peep = &get_sprite(sprite_id)->peep;
        if (order_id & 0x80)
        { // change costume
            uint8 sprite_type = order_id & ~0x80;
            sprite_type += 4;
            if (sprite_type >= Util::CountOf(peep_slow_walking_types))
            {
                log_error("Invalid change costume order for sprite_type %u", sprite_type);
                *ebx = MONEY32_UNDEFINED;
                return;
            }
            peep->sprite_type = sprite_type;
            peep->peep_flags &= ~PEEP_FLAGS_SLOW_WALK;
            if (peep_slow_walking_types[sprite_type])
            {
                peep->peep_flags |= PEEP_FLAGS_SLOW_WALK;
            }
            peep->action_frame = 0;
            peep_update_current_action_sprite_type(peep);
            invalidate_sprite_2((rct_sprite *)peep);
            window_invalidate_by_number(WC_PEEP, sprite_id);
            window_invalidate_by_class(WC_STAFF_LIST);
        }
        else
        {
            peep->staff_orders = order_id;
            window_invalidate_by_number(WC_PEEP, sprite_id);
            window_invalidate_by_class(WC_STAFF_LIST);
        }
    }
    *ebx = 0;
}

/**
 *
 *  rct2: 0x006C09D1
 */
void game_command_set_staff_patrol(sint32 * eax, sint32 * ebx, sint32 * ecx, sint32 * edx, sint32 * esi, sint32 * edi,
                                   sint32 * ebp)
{
    if (*ebx & GAME_COMMAND_FLAG_APPLY)
    {
        sint32 x         = *eax;
        sint32 y         = *ecx;
        uint16 sprite_id = *edx;
        if (sprite_id >= MAX_SPRITES)
        {
            *ebx = MONEY32_UNDEFINED;
            log_warning("Invalid sprite id %u", sprite_id);
            return;
        }
        rct_sprite * sprite = get_sprite(sprite_id);
        if (sprite->unknown.sprite_identifier != SPRITE_IDENTIFIER_PEEP || sprite->peep.type != PEEP_TYPE_STAFF)
        {
            *ebx = MONEY32_UNDEFINED;
            log_warning("Invalid type of sprite %u for game command", sprite_id);
            return;
        }
        rct_peep * peep         = &sprite->peep;
        sint32     patrolOffset = peep->staff_id * STAFF_PATROL_AREA_SIZE;

        staff_toggle_patrol_area(peep->staff_id, x, y);

        sint32 ispatrolling = 0;
        for (sint32 i = 0; i < 128; i++)
        {
            ispatrolling |= gStaffPatrolAreas[patrolOffset + i];
        }

        gStaffModes[peep->staff_id] &= ~2;
        if (ispatrolling)
        {
            gStaffModes[peep->staff_id] |= 2;
        }

        for (sint32 y2 = 0; y2 < 4; y2++)
        {
            for (sint32 x2 = 0; x2 < 4; x2++)
            {
                map_invalidate_tile_full((x & 0x1F80) + (x2 * 32), (y & 0x1F80) + (y2 * 32));
            }
        }
        staff_update_greyed_patrol_areas();
    }
    *ebx = 0;
}

/**
 *
 *  rct2: 0x006C0B83
 */
void game_command_fire_staff_member(sint32 * eax, sint32 * ebx, sint32 * ecx, sint32 * edx, sint32 * esi, sint32 * edi,
                                    sint32 * ebp)
{
    gCommandExpenditureType = RCT_EXPENDITURE_TYPE_WAGES;
    if (*ebx & GAME_COMMAND_FLAG_APPLY)
    {
        window_close_by_class(WC_FIRE_PROMPT);
        uint16 sprite_id = *edx;
        if (sprite_id >= MAX_SPRITES)
        {
            log_warning("Invalid game command, sprite_id = %u", sprite_id);
            *ebx = MONEY32_UNDEFINED;
            return;
        }
        rct_peep * peep = &get_sprite(sprite_id)->peep;
        if (peep->sprite_identifier != SPRITE_IDENTIFIER_PEEP || peep->type != PEEP_TYPE_STAFF)
        {
            log_warning("Invalid game command, peep->sprite_identifier = %u, peep->type = %u", peep->sprite_identifier,
                        peep->type);
            *ebx = MONEY32_UNDEFINED;
            return;
        }
        peep_sprite_remove(peep);
    }
    *ebx = 0;
}

/**
 * Hires a new staff member of the given type. If the hire cannot be completed (eg. the maximum
 * number of staff is reached or there are too many people in the game) it returns 0xFFFF.
 */
uint16 hire_new_staff_member(uint8 staffType)
{
    gGameCommandErrorTitle = STR_CANT_HIRE_NEW_STAFF;

    sint32 command_x, ebx, command_y, command_z, esi, new_sprite_index, ebp;
    command_y = command_z = esi = new_sprite_index = ebp = 0;
    command_x                                            = 0x8000;

    sint32 autoposition = gConfigGeneral.auto_staff_placement;
    if (gInputPlaceObjectModifier & PLACE_OBJECT_MODIFIER_SHIFT_Z)
    {
        autoposition = autoposition ^ 1;
    }

    ebx = autoposition << 16 | staffType << 8 | GAME_COMMAND_FLAG_APPLY;

    game_command_callback = game_command_callback_hire_new_staff_member;
    sint32 result = game_do_command_p(GAME_COMMAND_HIRE_NEW_STAFF_MEMBER, &command_x, &ebx, &command_y, &command_z, &esi,
                                      &new_sprite_index, &ebp);

    if (result == MONEY32_UNDEFINED)
        return SPRITE_INDEX_NULL;

    if ((staffType == STAFF_TYPE_HANDYMAN) && gConfigGeneral.handymen_mow_default)
    {
        rct_peep * newPeep    = GET_PEEP(new_sprite_index);
        uint8      new_orders = newPeep->staff_orders | STAFF_ORDERS_MOWING;
        game_do_command(newPeep->x, ((sint32)new_orders << 8) | GAME_COMMAND_FLAG_APPLY, newPeep->y, new_sprite_index,
                        GAME_COMMAND_SET_STAFF_ORDER, 0, 0);
    }

    return new_sprite_index;
}

/**
 *
 *  rct2: 0x006C0C3F
 */
void staff_update_greyed_patrol_areas()
{
    rct_peep * peep;

    for (sint32 staff_type = 0; staff_type < STAFF_TYPE_COUNT; ++staff_type)
    {
        sint32 staffPatrolOffset = (staff_type + STAFF_MAX_COUNT) * STAFF_PATROL_AREA_SIZE;
        for (sint32 i = 0; i < STAFF_PATROL_AREA_SIZE; i++)
        {
            gStaffPatrolAreas[staffPatrolOffset + i] = 0;
        }

        for (uint16 sprite_index = gSpriteListHead[SPRITE_LIST_PEEP]; sprite_index != SPRITE_INDEX_NULL;
             sprite_index        = peep->next)
        {
            peep = GET_PEEP(sprite_index);

            if (peep->type == PEEP_TYPE_STAFF && staff_type == peep->staff_type)
            {
                sint32 peepPatrolOffset = peep->staff_id * STAFF_PATROL_AREA_SIZE;
                for (sint32 i = 0; i < STAFF_PATROL_AREA_SIZE; i++)
                {
                    gStaffPatrolAreas[staffPatrolOffset + i] |= gStaffPatrolAreas[peepPatrolOffset + i];
                }
            }
        }
    }
}

static bool staff_is_location_in_patrol_area(rct_peep * peep, sint32 x, sint32 y)
{
    // 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
    return staff_is_patrol_area_set(peep->staff_id, x, y);
}

/**
 *
 *  rct2: 0x006C0905
 */
bool staff_is_location_in_patrol(rct_peep * staff, sint32 x, sint32 y)
{
    // Check if location is in the park
    if (!map_is_location_owned_or_has_rights(x, y))
        return false;

    // Check if staff has patrol area
    if (!(gStaffModes[staff->staff_id] & 2))
        return true;

    return staff_is_location_in_patrol_area(staff, x, y);
}

bool staff_is_location_on_patrol_edge(rct_peep * mechanic, sint32 x, sint32 y)
{
    // Check whether the location x,y is inside and on the edge of the
    // patrol zone for mechanic.
    bool   onZoneEdge   = false;
    sint32 neighbourDir = 0;
    while (!onZoneEdge && neighbourDir <= 7)
    {
        sint32 neighbourX = x + TileDirectionDelta[neighbourDir].x;
        sint32 neighbourY = y + TileDirectionDelta[neighbourDir].y;
        onZoneEdge        = !staff_is_location_in_patrol(mechanic, neighbourX, neighbourY);
        neighbourDir++;
    }
    return onZoneEdge;
}

bool staff_can_ignore_wide_flag(rct_peep * staff, sint32 x, sint32 y, uint8 z, rct_tile_element * 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->type != PEEP_TYPE_STAFF)
        return false;

    if (!staff_is_location_on_patrol_edge(staff, x, y))
    {
        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 total     = 0;
    uint8 pathcount = 0;
    uint8 widecount = 0;
    for (sint32 adjac_dir = 0; adjac_dir <= 3; adjac_dir++)
    {
        sint32 adjac_x = x + TileDirectionDelta[adjac_dir].x;
        sint32 adjac_y = y + TileDirectionDelta[adjac_dir].y;
        uint8  adjac_z = z;

        /* Ignore adjacent tiles outside the patrol zone. */
        if (!staff_is_location_in_patrol(staff, adjac_x, adjac_y))
            continue;

        /* Ignore adjacent tiles on the patrol zone edge. */
        if (staff_is_location_on_patrol_edge(staff, adjac_x, adjac_y))
            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->properties.path.edges & (1u << adjac_dir)))
        {
            continue;
        }

        if (footpath_element_is_sloped(path))
        {
            if (footpath_element_get_slope_direction(path) == adjac_dir)
            {
                adjac_z = z + 2;
            }
        }

        /* Search through all adjacent map elements */
        rct_tile_element * test_element = map_get_first_element_at(adjac_x / 32, adjac_y / 32);
        bool              pathfound    = false;
        bool              widefound    = false;
        do
        {
            if (tile_element_get_type(test_element) != TILE_ELEMENT_TYPE_PATH)
            {
                continue;
            }

            /* test_element is a path */
            if (!is_valid_path_z_and_direction(test_element, adjac_z, adjac_dir))
                continue;

            /* test_element is a connected path */
            if (!pathfound)
            {
                pathfound = true;
                pathcount++;
            }

            if (footpath_element_is_wide(test_element))
            {
                if (!widefound)
                {
                    widefound = true;
                    widecount++;
                }
            }
        } while (!tile_element_is_last_for_tile(test_element++));
    }

    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 staff_get_valid_patrol_directions(rct_peep * peep, sint16 x, sint16 y)
{
    uint8 directions = 0;

    if (staff_is_location_in_patrol(peep, x - 32, y))
    {
        directions |= (1 << 0);
    }

    if (staff_is_location_in_patrol(peep, x, y + 32))
    {
        directions |= (1 << 1);
    }

    if (staff_is_location_in_patrol(peep, x + 32, y))
    {
        directions |= (1 << 2);
    }

    if (staff_is_location_in_patrol(peep, x, y - 32))
    {
        directions |= (1 << 3);
    }

    if (directions == 0)
    {
        directions = 0xF;
    }

    return directions;
}

/**
 *
 *  rct2: 0x006C1955
 */
void staff_reset_stats()
{
    uint16     spriteIndex;
    rct_peep * peep;

    FOR_ALL_STAFF(spriteIndex, peep)
    {
        peep->time_in_park          = gDateMonthsElapsed;
        peep->staff_lawns_mown      = 0;
        peep->staff_rides_fixed     = 0;
        peep->staff_gardens_watered = 0;
        peep->staff_rides_inspected = 0;
        peep->staff_litter_swept    = 0;
        peep->staff_bins_emptied    = 0;
    }
}

bool staff_is_patrol_area_set(sint32 staffIndex, sint32 x, sint32 y)
{
    x = (x & 0x1F80) >> 7;
    y = (y & 0x1F80) >> 1;

    sint32 peepOffset = staffIndex * STAFF_PATROL_AREA_SIZE;
    sint32 offset     = (x | y) >> 5;
    sint32 bitIndex   = (x | y) & 0x1F;
    return gStaffPatrolAreas[peepOffset + offset] & (((uint32)1) << bitIndex);
}

void staff_set_patrol_area(sint32 staffIndex, sint32 x, sint32 y, bool value)
{
    x = (x & 0x1F80) >> 7;
    y = (y & 0x1F80) >> 1;

    sint32   peepOffset = staffIndex * STAFF_PATROL_AREA_SIZE;
    sint32   offset     = (x | y) >> 5;
    sint32   bitIndex   = (x | y) & 0x1F;
    uint32 * addr       = &gStaffPatrolAreas[peepOffset + offset];
    if (value)
    {
        *addr |= (1 << bitIndex);
    }
    else
    {
        *addr &= ~(1 << bitIndex);
    }
}

void staff_toggle_patrol_area(sint32 staffIndex, sint32 x, sint32 y)
{
    x = (x & 0x1F80) >> 7;
    y = (y & 0x1F80) >> 1;

    sint32 peepOffset = staffIndex * STAFF_PATROL_AREA_SIZE;
    sint32 offset     = (x | y) >> 5;
    sint32 bitIndex   = (x | y) & 0x1F;
    gStaffPatrolAreas[peepOffset + offset] ^= (1 << bitIndex);
}

/**
 *
 *  rct2: 0x006BFBE8
 *
 * Returns 0xFF when no nearby litter or unpathable litter
 */
static uint8 staff_handyman_direction_to_nearest_litter(rct_peep * peep)
{
    uint16       nearestLitterDist = (uint16)-1;
    rct_litter * nearestLitter     = nullptr;
    rct_litter * litter            = nullptr;

    for (uint16 litterIndex = gSpriteListHead[SPRITE_LIST_LITTER]; litterIndex != 0xFFFF; litterIndex = litter->next)
    {
        litter = &get_sprite(litterIndex)->litter;

        uint16 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 0xFF;
    }

    LocationXY16 litterTile = { static_cast<sint16>(nearestLitter->x & 0xFFE0), static_cast<sint16>(nearestLitter->y & 0xFFE0) };

    if (!staff_is_location_in_patrol(peep, litterTile.x, litterTile.y))
    {
        return 0xFF;
    }

    litterTile.x += 16;
    litterTile.y += 16;

    sint16 x_diff = litterTile.x - peep->x;
    sint16 y_diff = litterTile.y - peep->y;

    uint8 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<sint32>((nearestLitter->x & 0xFFE0) - TileDirectionDelta[nextDirection].x),
                          static_cast<sint32>((nearestLitter->y & 0xFFE0) - TileDirectionDelta[nextDirection].y) };

    sint16 nextZ = ((peep->z + 8) & 0xFFF0) / 8;

    rct_tile_element * tileElement = map_get_first_element_at(nextTile.x / 32, nextTile.y / 32);

    do
    {
        if (tileElement->base_height != nextZ)
            continue;
        if (tile_element_get_type(tileElement) == TILE_ELEMENT_TYPE_ENTRANCE ||
            tile_element_get_type(tileElement) == TILE_ELEMENT_TYPE_TRACK)
        {
            return 0xFF;
        }
    } while (!tile_element_is_last_for_tile(tileElement++));

    nextTile.x = (peep->x & 0xFFE0) + TileDirectionDelta[nextDirection].x;
    nextTile.y = (peep->y & 0xFFE0) + TileDirectionDelta[nextDirection].y;

    tileElement = map_get_first_element_at(nextTile.x / 32, nextTile.y / 32);

    do
    {
        if (tileElement->base_height != nextZ)
            continue;
        if (tile_element_get_type(tileElement) == TILE_ELEMENT_TYPE_ENTRANCE ||
            tile_element_get_type(tileElement) == TILE_ELEMENT_TYPE_TRACK)
        {
            return 0xFF;
        }
    } while (!tile_element_is_last_for_tile(tileElement++));

    return nextDirection;
}


/**
 *
 *  rct2: 0x006BF931
 */
static uint8 staff_handyman_direction_to_uncut_grass(rct_peep * peep, uint8 valid_directions)
{
    if (!(peep->next_var_29 & 0x18))
    {

        rct_tile_element * tileElement = map_get_surface_element_at({peep->next_x, peep->next_y});

        if (peep->next_z != tileElement->base_height)
            return 0xFF;

        if (peep->next_var_29 & 0x4)
        {
            if ((tileElement->properties.surface.slope & TILE_ELEMENT_SURFACE_SLOPE_MASK) != byte_98D800[peep->next_var_29 & 0x3])
                return 0xFF;
        }
        else if ((tileElement->properties.surface.slope & TILE_ELEMENT_SURFACE_SLOPE_MASK) != 0)
            return 0xFF;
    }

    uint8 chosenDirection = scenario_rand() & 0x3;
    for (uint8 i = 0; i < 4; ++i, ++chosenDirection)
    {
        chosenDirection &= 0x3;

        if (!(valid_directions & (1 << chosenDirection)))
        {
            continue;
        }

        CoordsXY chosenTile = { static_cast<sint32>(peep->next_x + TileDirectionDelta[chosenDirection].x),
                                static_cast<sint32>(peep->next_y + TileDirectionDelta[chosenDirection].y) };

        if (chosenTile.x > 0x1FFF || chosenTile.y > 0x1FFF)
            continue;

        rct_tile_element * tileElement = map_get_surface_element_at(chosenTile);

        if (tile_element_get_terrain(tileElement) != 0)
            continue;

        if (abs(tileElement->base_height - peep->next_z) > 2)
            continue;

        if ((tileElement->properties.surface.grass_length & 0x7) < GRASS_LENGTH_CLEAR_1)
            continue;

        return chosenDirection;
    }
    return 0xFF;
}

/**
 *
 *  rct2: 0x006BFD9C
 */
static sint32 staff_handyman_direction_rand_surface(rct_peep * peep, uint8 validDirections)
{
    uint8 direction = scenario_rand() & 3;
    for (sint32 i = 0; i < 4; ++i, ++direction)
    {
        direction &= 3;
        if (!(validDirections & (1 << direction)))
            continue;

        LocationXY16 chosenTile = { static_cast<sint16>(peep->next_x + TileDirectionDelta[direction].x),
                                static_cast<sint16>(peep->next_y + TileDirectionDelta[direction].y) };

        if (map_surface_is_blocked(chosenTile.x, chosenTile.y))
            continue;

        break;
    }
    // If it tries all directions this is required
    // to make it back to the first direction and
    // override validDirections
    direction &= 3;
    return direction;
}

/**
 *
 *  rct2: 0x006BFBA8
 */
static bool staff_path_finding_handyman(rct_peep * peep)
{
    peep->var_E2++;

    uint8 litterDirection = 0xFF;
    uint8 validDirections = staff_get_valid_patrol_directions(peep, peep->next_x, peep->next_y);

    if ((peep->staff_orders & STAFF_ORDERS_SWEEPING) && ((gCurrentTicks + peep->sprite_index) & 0xFFF) > 110)
    {
        litterDirection = staff_handyman_direction_to_nearest_litter(peep);
    }

    uint8 direction = 0xFF;
    if (litterDirection == 0xFF && (peep->staff_orders & STAFF_ORDERS_MOWING) && peep->var_E2 >= 12)
    {
        direction = staff_handyman_direction_to_uncut_grass(peep, validDirections);
    }

    if (direction == 0xFF)
    {
        if (peep->next_var_29 & 0x18)
        {
            direction = staff_handyman_direction_rand_surface(peep, validDirections);
        }
        else
        {
            rct_tile_element * tileElement = map_get_path_element_at(peep->next_x / 32, peep->next_y / 32, peep->next_z);

            if (tileElement == nullptr)
                return true;

            uint8 pathDirections = (tileElement->properties.path.edges & validDirections) & 0xF;
            if (pathDirections == 0)
            {
                direction = staff_handyman_direction_rand_surface(peep, validDirections);
            }
            else
            {
                bool chooseRandom = true;
                if (litterDirection != 0xFF && pathDirections & (1 << litterDirection))
                {
                    if ((scenario_rand() & 0xFFFF) >= 0x1999)
                    {
                        chooseRandom = false;
                        direction    = litterDirection;
                    }
                }
                else
                {
                    pathDirections &= ~(1 << (peep->direction ^ (1 << 1)));
                    if (pathDirections == 0)
                    {
                        pathDirections |= 1 << (peep->direction ^ (1 << 1));
                    }
                }

                if (chooseRandom == true)
                {
                    do
                    {
                        direction = scenario_rand() & 3;
                    } while ((pathDirections & (1 << direction)) == 0);
                }
            }
        }
    }

    // countof(TileDirectionDelta)
    assert(direction < 8);

    LocationXY16 chosenTile = { static_cast<sint16>(peep->next_x + TileDirectionDelta[direction].x),
                            static_cast<sint16>(peep->next_y + TileDirectionDelta[direction].y) };

    while (chosenTile.x > 0x1FFF || chosenTile.y > 0x1FFF)
    {
        direction    = staff_handyman_direction_rand_surface(peep, validDirections);
        chosenTile.x = peep->next_x + TileDirectionDelta[direction].x;
        chosenTile.y = peep->next_y + TileDirectionDelta[direction].y;
    }

    peep->direction             = direction;
    peep->destination_x         = chosenTile.x + 16;
    peep->destination_y         = chosenTile.y + 16;
    peep->destination_tolerance = 3;
    if (peep->state == PEEP_STATE_QUEUING)
    {
        peep->destination_tolerance = (scenario_rand() & 7) + 2;
    }
    return false;
}

static uint8 staff_direction_surface(rct_peep * peep, uint8 initialDirection)
{
    uint8 direction = initialDirection;
    for (sint32 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->next_x, peep->next_y, peep->next_z, peep->next_z + 4, direction) == true)
            continue;

        if (fence_in_the_way(peep->next_x, peep->next_y, peep->next_z, peep->next_z + 4, direction ^ (1 << 1)) == true)
            continue;

        LocationXY16 chosenTile = { static_cast<sint16>(peep->next_x + TileDirectionDelta[direction].x),
                                static_cast<sint16>(peep->next_y + TileDirectionDelta[direction].y) };

        if (map_surface_is_blocked(chosenTile.x, chosenTile.y) == false)
        {
            return direction;
        }
    }
    return initialDirection;
}

/**
 *
 *  rct2: 0x006BFF45
 */
static uint8 staff_mechanic_direction_surface(rct_peep * peep)
{
    uint8 direction = scenario_rand() & 3;

    if ((peep->state == PEEP_STATE_ANSWERING || peep->state == PEEP_STATE_HEADING_TO_INSPECTION) && scenario_rand() & 1)
    {
        TileCoordsXYZD location = ride_get_exit_location(peep->current_ride, peep->current_ride_station);
        if (location.isNull())
        {
            location = ride_get_entrance_location(peep->current_ride, peep->current_ride_station);
        }

        LocationXY16 chosenTile = { static_cast<sint16>(location.x * 32), static_cast<sint16>(location.y * 32) };

        sint16 x_diff = chosenTile.x - peep->x;
        sint16 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 staff_mechanic_direction_path_rand(rct_peep * peep, uint8 pathDirections)
{
    if (scenario_rand() & 1)
    {
        if (pathDirections & (1 << peep->direction))
            return peep->direction;
    }

    // Modified from original to spam scenario_rand less
    uint8 direction = scenario_rand() & 3;
    for (sint32 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->direction;
}

/**
 *
 *  rct2: 0x006C0121
 */
static uint8 staff_mechanic_direction_path(rct_peep * peep, uint8 validDirections, rct_tile_element * pathElement)
{

    uint8 direction      = 0xFF;
    uint8 pathDirections = pathElement->properties.path.edges & 0xF;
    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 << (peep->direction ^ (1 << 1)));
    if (pathDirections == 0)
    {
        pathDirections |= (1 << (peep->direction ^ (1 << 1)));
    }

    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->sub_state != 2)
        {
            return direction;
        }
        peep->sub_state = 3;
    }

    pathDirections |= (1 << direction);

    // Mechanic is heading to ride (either broken down or for inspection).
    if (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(peep->current_ride, peep->current_ride_station);
        if (location.isNull())
        {
            location = ride_get_entrance_location(peep->current_ride, peep->current_ride_station);

            // If no entrance is present either. This is an incorrect state.
            if (location.isNull())
            {
                return staff_mechanic_direction_path_rand(peep, pathDirections);
            }
        }

        gPeepPathFindGoalPosition.x = (sint16)(location.x * 32);
        gPeepPathFindGoalPosition.y = (sint16)(location.y * 32);
        gPeepPathFindGoalPosition.z = (sint16)location.z;

        gPeepPathFindIgnoreForeignQueues = false;
        gPeepPathFindQueueRideIndex      = 255;

#if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
        pathfind_logging_enable(peep);
#endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1

        sint32 pathfindDirection = peep_pathfind_choose_direction(peep->next_x, peep->next_y, peep->next_z, peep);

#if defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1
        pathfind_logging_disable();
#endif // defined(DEBUG_LEVEL_1) && DEBUG_LEVEL_1

        if (pathfindDirection == -1)
        {
            /* Heuristic search failed for all directions.
             * Reset the pathfind_goal - this means that the pathfind_history
             * 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 (uint8)pathfindDirection;
    }
    return staff_mechanic_direction_path_rand(peep, pathDirections);
}

/**
 *
 *  rct2: 0x006BFF2C
 */
static bool staff_path_finding_mechanic(rct_peep * peep)
{
    uint8 validDirections = staff_get_valid_patrol_directions(peep, peep->next_x, peep->next_y);
    uint8 direction       = 0xFF;
    if (peep->next_var_29 & 0x18)
    {
        direction = staff_mechanic_direction_surface(peep);
    }
    else
    {
        rct_tile_element * pathElement = map_get_path_element_at(peep->next_x / 32, peep->next_y / 32, peep->next_z);
        if (pathElement == nullptr)
            return true;

        direction = staff_mechanic_direction_path(peep, validDirections, pathElement);
    }

    // countof(TileDirectionDelta)
    assert(direction < 8);

    LocationXY16 chosenTile = { static_cast<sint16>(peep->next_x + TileDirectionDelta[direction].x),
                            static_cast<sint16>(peep->next_y + TileDirectionDelta[direction].y) };

    while (chosenTile.x > 0x1FFF || chosenTile.y > 0x1FFF)
    {
        direction    = staff_mechanic_direction_surface(peep);
        chosenTile.x = peep->next_x + TileDirectionDelta[direction].x;
        chosenTile.y = peep->next_y + TileDirectionDelta[direction].y;
    }

    peep->direction             = direction;
    peep->destination_x         = chosenTile.x + 16;
    peep->destination_y         = chosenTile.y + 16;
    peep->destination_tolerance = (scenario_rand() & 7) + 2;

    return false;
}

/**
 *
 *  rct2: 0x006C050B
 */
static uint8 staff_direction_path(rct_peep * peep, uint8 validDirections, rct_tile_element * pathElement)
{
    uint8 direction      = 0xFF;
    uint8 pathDirections = pathElement->properties.path.edges & 0xF;
    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 << (peep->direction ^ (1 << 1)));
    if (pathDirections == 0)
    {
        pathDirections |= (1 << (peep->direction ^ (1 << 1)));
    }

    direction = bitscanforward(pathDirections);
    pathDirections &= ~(1 << direction);
    if (pathDirections == 0)
    {
        return direction;
    }

    pathDirections |= (1 << direction);

    direction = scenario_rand() & 3;
    for (sint32 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
 */
static bool staff_path_finding_misc(rct_peep * peep)
{
    uint8 validDirections = staff_get_valid_patrol_directions(peep, peep->next_x, peep->next_y);

    uint8 direction = 0xFF;
    if (peep->next_var_29 & 0x18)
    {
        direction = staff_direction_surface(peep, scenario_rand() & 3);
    }
    else
    {
        rct_tile_element * pathElement = map_get_path_element_at(peep->next_x / 32, peep->next_y / 32, peep->next_z);
        if (pathElement == nullptr)
            return true;

        direction = staff_direction_path(peep, validDirections, pathElement);
    }

    LocationXY16 chosenTile = { static_cast<sint16>(peep->next_x + TileDirectionDelta[direction].x),
                            static_cast<sint16>(peep->next_y + TileDirectionDelta[direction].y) };

    while (chosenTile.x > 0x1FFF || chosenTile.y > 0x1FFF)
    {
        direction    = staff_direction_surface(peep, scenario_rand() & 3);
        chosenTile.x = peep->next_x + TileDirectionDelta[direction].x;
        chosenTile.y = peep->next_y + TileDirectionDelta[direction].y;
    }

    peep->direction             = direction;
    peep->destination_x         = chosenTile.x + 16;
    peep->destination_y         = chosenTile.y + 16;
    peep->destination_tolerance = (scenario_rand() & 7) + 2;

    return false;
}

/**
 *
 *  rct2: 0x006C086D
 */
static void staff_entertainer_update_nearby_peeps(rct_peep * peep)
{
    uint16     spriteIndex;
    rct_peep * guest;

    FOR_ALL_GUESTS(spriteIndex, guest)
    {
        if (guest->x == LOCATION_NULL)
            continue;

        sint16 z_dist = abs(peep->z - guest->z);
        if (z_dist > 48)
            continue;

        sint16 x_dist = abs(peep->x - guest->x);
        sint16 y_dist = abs(peep->y - guest->y);

        if (x_dist > 96)
            continue;

        if (y_dist > 96)
            continue;

        if (peep->state == PEEP_STATE_WALKING)
        {
            peep->happiness_target = Math::Min(peep->happiness_target + 4, PEEP_MAX_HAPPINESS);
        }
        else if (peep->state == PEEP_STATE_QUEUING)
        {
            if (peep->time_in_queue > 200)
            {
                peep->time_in_queue -= 200;
            }
            else
            {
                peep->time_in_queue = 0;
            }
            peep->happiness_target = Math::Min(peep->happiness_target + 3, PEEP_MAX_HAPPINESS);
        }
    }
}

/**
 *
 *  rct2: 0x006C05AE
 */
static sint32 staff_path_finding_entertainer(rct_peep * peep)
{

    if (((scenario_rand() & 0xFFFF) <= 0x4000) && (peep->action == PEEP_ACTION_NONE_1 || peep->action == PEEP_ACTION_NONE_2))
    {

        invalidate_sprite_2((rct_sprite *)peep);

        peep->action                     = (scenario_rand() & 1) ? PEEP_ACTION_WAVE_2 : PEEP_ACTION_JOY;
        peep->action_frame               = 0;
        peep->action_sprite_image_offset = 0;

        peep_update_current_action_sprite_type(peep);
        invalidate_sprite_2((rct_sprite *)peep);
        staff_entertainer_update_nearby_peeps(peep);
    }

    return staff_path_finding_misc(peep);
}

/**
 *
 *  rct2: 0x006BF926
 */
sint32 staff_path_finding(rct_peep * peep)
{
    switch (peep->staff_type)
    {
    case STAFF_TYPE_HANDYMAN:
        return staff_path_finding_handyman(peep);
    case STAFF_TYPE_MECHANIC:
        return staff_path_finding_mechanic(peep);
    case STAFF_TYPE_SECURITY:
        return staff_path_finding_misc(peep);
    case STAFF_TYPE_ENTERTAINER:
        return staff_path_finding_entertainer(peep);

    default:
        assert(false);
        return 0;
    }
}

void game_command_pickup_staff(sint32 * eax, sint32 * ebx, sint32 * ecx, sint32 * edx, sint32 * esi, sint32 * edi, sint32 * ebp)
{
    sint32 peepnum = *eax;
    sint32 x       = *edi;
    sint32 y       = *ebp;
    sint32 z       = *edx;
    sint32 action  = *ecx;
    if (peep_pickup_command(peepnum, x, y, z, action, *ebx & GAME_COMMAND_FLAG_APPLY))
    {
        *ebx = 0;
    }
    else
    {
        *ebx = MONEY32_UNDEFINED;
    }
}

colour_t staff_get_colour(uint8 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 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 staff_get_available_entertainer_costumes()
{
    uint32 entertainerCostumes = 0;
    for (sint32 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;
}

sint32 staff_get_available_entertainer_costume_list(uint8 * costumeList)
{
    uint32 availableCostumes = staff_get_available_entertainer_costumes();
    sint32 numCostumes       = 0;
    for (uint8 i = 0; i < ENTERTAINER_COSTUME_COUNT; i++)
    {
        if (availableCostumes & (1 << i))
        {
            costumeList[numCostumes++] = i;
        }
    }
    return numCostumes;
}
