/*****************************************************************************
 * 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 "../Context.h"
#include "../Game.h"
#include "../OpenRCT2.h"
#include "../audio/audio.h"
#include "../config/Config.h"
#include "../core/Guard.hpp"
#include "../interface/Window_internal.h"
#include "../localisation/Localisation.h"
#include "../management/Finance.h"
#include "../management/Marketing.h"
#include "../management/NewsItem.h"
#include "../network/network.h"
#include "../ride/Ride.h"
#include "../ride/RideData.h"
#include "../ride/ShopItem.h"
#include "../ride/Station.h"
#include "../ride/Track.h"
#include "../scenario/Scenario.h"
#include "../util/Util.h"
#include "../windows/Intent.h"
#include "../world/Climate.h"
#include "../world/Footpath.h"
#include "../world/LargeScenery.h"
#include "../world/Park.h"
#include "../world/Scenery.h"
#include "../world/Sprite.h"
#include "../world/Surface.h"
#include "Peep.h"

#include <algorithm>
#include <iterator>

// Locations of the spiral slide platform that a peep walks from the entrance of the ride to the
// entrance of the slide. Up to 4 waypoints for each 4 sides that an ride entrance can be located
// and 4 different rotations of the ride. 4 * 4 * 4 = 64 locations.
// clang-format off
static constexpr const CoordsXY SpiralSlideWalkingPath[64] = {
    {  56,   8 },
    {   8,   8 },
    {   8,  32 },
    {  32,  32 },
    {   8,   8 },
    {   8,   8 },
    {   8,  32 },
    {  32,  32 },
    {   8,  32 },
    {   8,  32 },
    {   8,  32 },
    {  32,  32 },
    {   8,  56 },
    {   8,  32 },
    {   8,  32 },
    {  32,  32 },
    {  56,  24 },
    {  32,  24 },
    {  32,  24 },
    {  32,   0 },
    {  56, -24 },
    {  56,  24 },
    {  32,  24 },
    {  32,   0 },
    {   8,  24 },
    {  32,  24 },
    {  32,  24 },
    {  32,   0 },
    {  32,  24 },
    {  32,  24 },
    {  32,  24 },
    {  32,   0 },
    {  24,   0 },
    {  24,   0 },
    {  24,   0 },
    {   0,   0 },
    {  24, -24 },
    {  24,   0 },
    {  24,   0 },
    {   0,   0 },
    { -24, -24 },
    {  24, -24 },
    {  24,   0 },
    {   0,   0 },
    {  24,  24 },
    {  24,   0 },
    {  24,   0 },
    {   0,   0 },
    {  24,   8 },
    {   0,   8 },
    {   0,   8 },
    {   0,  32 },
    {   0,   8 },
    {   0,   8 },
    {   0,   8 },
    {   0,  32 },
    { -24,   8 },
    {   0,   8 },
    {   0,   8 },
    {   0,  32 },
    { -24,  56 },
    { -24,   8 },
    {   0,   8 },
    {   0,  32 },
};

/** rct2: 0x00981F4C, 0x00981F4E */
static constexpr const CoordsXY _WatchingPositionOffsets[] = {
    {  7,  5 },
    {  5, 25 },
    { 25,  5 },
    {  5,  7 },
    {  7,  9 },
    {  9, 25 },
    { 25,  9 },
    {  9,  7 },
    {  7, 23 },
    { 23, 25 },
    { 25, 23 },
    { 23,  7 },
    {  7, 27 },
    { 27, 25 },
    { 25, 27 },
    { 27,  7 },
    {  7,  0 },
    {  0, 25 },
    { 25,  0 },
    {  0,  7 },
    {  7,  0 },
    {  0, 25 },
    { 25,  0 },
    {  0,  7 },
    {  7,  0 },
    {  0, 25 },
    { 25,  0 },
    {  0,  7 },
    {  7,  0 },
    {  0, 25 },
    { 25,  0 },
    {  0,  7 },
};

static constexpr const ride_rating NauseaMaximumThresholds[] = {
    300, 600, 800, 1000
};

/** rct2: 0x0097EFCC */
static constexpr const uint8_t item_standard_litter[32] = {
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_BALLOON
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_TOY
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_MAP
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_PHOTO
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_UMBRELLA
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_DRINK
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_BURGER
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_CHIPS
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_ICE_CREAM
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_CANDYFLOSS
    LITTER_TYPE_EMPTY_CAN,        // PEEP_ITEM_EMPTY_CAN
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_RUBBISH
    LITTER_TYPE_EMPTY_BURGER_BOX, // PEEP_ITEM_EMPTY_BURGER_BOX
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_PIZZA
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_VOUCHER
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_POPCORN
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_HOT_DOG
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_TENTACLE
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_HAT
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_TOFFEE_APPLE
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_TSHIRT
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_DOUGHNUT
    LITTER_TYPE_RUBBISH,          // PEEP_ITEM_COFFEE
    LITTER_TYPE_EMPTY_CUP,        // PEEP_ITEM_EMPTY_CUP
    LITTER_TYPE_EMPTY_BOX,        // PEEP_ITEM_CHICKEN
    LITTER_TYPE_EMPTY_BOTTLE,     // PEEP_ITEM_LEMONADE
    LITTER_TYPE_EMPTY_BOX,        // PEEP_ITEM_EMPTY_BOX
    LITTER_TYPE_EMPTY_BOTTLE,     // PEEP_ITEM_EMPTY_BOTTLE
};

/** rct2: 0x0097EFE8 */
static constexpr const uint8_t item_extra_litter[32] = {
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_PHOTO2
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_PHOTO3
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_PHOTO4
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_PRETZEL
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_CHOCOLATE
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_ICED_TEA
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_FUNNEL_CAKE
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_SUNGLASSES
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_BEEF_NOODLES
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_FRIED_RICE_NOODLES
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_WONTON_SOUP
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_MEATBALL_SOUP
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_FRUIT_JUICE
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_SOYBEAN_MILK
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_SU_JONGKWA
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_SUB_SANDWICH
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_COOKIE
    LITTER_TYPE_EMPTY_BOWL_RED,     // PEEP_ITEM_EMPTY_BOWL_RED
    LITTER_TYPE_EMPTY_DRINK_CARTON, // PEEP_ITEM_EMPTY_DRINK_CARTON
    LITTER_TYPE_EMPTY_JUICE_CUP,    // PEEP_ITEM_EMPTY_JUICE_CUP
    LITTER_TYPE_RUBBISH,            // PEEP_ITEM_ROAST_SAUSAGE
    LITTER_TYPE_EMPTY_BOWL_BLUE,    // PEEP_ITEM_EMPTY_BOWL_BLUE
};

/** rct2: 0x009822F4, 0x00982310 */
static constexpr const uint8_t item_consumption_time[] = {
    0,      // SHOP_ITEM_BALLOON
    0,      // SHOP_ITEM_TOY
    0,      // SHOP_ITEM_MAP
    0,      // SHOP_ITEM_PHOTO
    0,      // SHOP_ITEM_UMBRELLA
    100,    // SHOP_ITEM_DRINK
    150,    // SHOP_ITEM_BURGER
    120,    // SHOP_ITEM_CHIPS
    60,     // SHOP_ITEM_ICE_CREAM
    50,     // SHOP_ITEM_CANDYFLOSS
    0,      // SHOP_ITEM_EMPTY_CAN
    0,      // SHOP_ITEM_RUBBISH
    0,      // SHOP_ITEM_EMPTY_BURGER_BOX
    150,    // SHOP_ITEM_PIZZA
    0,      // SHOP_ITEM_VOUCHER
    75,     // SHOP_ITEM_POPCORN
    133,    // SHOP_ITEM_HOT_DOG
    110,    // SHOP_ITEM_TENTACLE
    0,      // SHOP_ITEM_HAT
    50,     // SHOP_ITEM_TOFFEE_APPLE
    0,      // SHOP_ITEM_TSHIRT
    80,     // SHOP_ITEM_DOUGHNUT
    90,     // SHOP_ITEM_COFFEE
    0,      // SHOP_ITEM_EMPTY_CUP
    170,    // SHOP_ITEM_CHICKEN
    115,    // SHOP_ITEM_LEMONADE
    0,      // SHOP_ITEM_EMPTY_BOX
    0,      // SHOP_ITEM_EMPTY_BOTTLE
    0xFF,   // UNUSED
    0xFF,   // UNUSED
    0xFF,   // UNUSED
    0xFF,   // UNUSED
    0,      // SHOP_ITEM_PHOTO2
    0,      // SHOP_ITEM_PHOTO3
    0,      // SHOP_ITEM_PHOTO4
    70,     // SHOP_ITEM_PRETZEL
    85,     // SHOP_ITEM_CHOCOLATE
    95,     // SHOP_ITEM_ICED_TEA
    90,     // SHOP_ITEM_FUNNEL_CAKE
    0,      // SHOP_ITEM_SUNGLASSES
    130,    // SHOP_ITEM_BEEF_NOODLES
    120,    // SHOP_ITEM_FRIED_RICE_NOODLES
    100,    // SHOP_ITEM_WONTON_SOUP
    110,    // SHOP_ITEM_MEATBALL_SOUP
    110,    // SHOP_ITEM_FRUIT_JUICE
    90,     // SHOP_ITEM_SOYBEAN_MILK
    100,    // SHOP_ITEM_SU_JONGKWA
    130,    // SHOP_ITEM_SUB_SANDWICH
    75,     // SHOP_ITEM_COOKIE
    0,      // SHOP_ITEM_EMPTY_BOWL_RED
    0,      // SHOP_ITEM_EMPTY_DRINK_CARTON
    0,      // SHOP_ITEM_EMPTY_JUICE_CUP
    115,    // SHOP_ITEM_ROAST_SAUSAGE
    0       // SHOP_ITEM_EMPTY_BOWL_BLUE
};

/** rct2: 009823AC */
static constexpr const PeepThoughtType crowded_thoughts[] = {
    PEEP_THOUGHT_TYPE_LOST,
    PEEP_THOUGHT_TYPE_TIRED,
    PEEP_THOUGHT_TYPE_BAD_LITTER,
    PEEP_THOUGHT_TYPE_HUNGRY,
    PEEP_THOUGHT_TYPE_THIRSTY,
    PEEP_THOUGHT_TYPE_VERY_CLEAN,
    PEEP_THOUGHT_TYPE_CROWDED,
    PEEP_THOUGHT_TYPE_SCENERY,
    PEEP_THOUGHT_TYPE_VERY_CLEAN,
    PEEP_THOUGHT_TYPE_MUSIC,
    PEEP_THOUGHT_TYPE_WATCHED,
    PEEP_THOUGHT_TYPE_NOT_HUNGRY,
    PEEP_THOUGHT_TYPE_NOT_THIRSTY,
    PEEP_THOUGHT_TYPE_BATHROOM,
    PEEP_THOUGHT_TYPE_NONE,
    PEEP_THOUGHT_TYPE_NONE,
};

/** rct2: 0x00982326 */
static constexpr const uint8_t peep_item_containers[] = {
    0xFF,                           // PEEP_ITEM_BALLOON
    0xFF,                           // PEEP_ITEM_TOY
    0xFF,                           // PEEP_ITEM_MAP
    0xFF,                           // PEEP_ITEM_PHOTO
    0xFF,                           // PEEP_ITEM_UMBRELLA
    SHOP_ITEM_EMPTY_CAN,            // PEEP_ITEM_DRINK
    SHOP_ITEM_EMPTY_BURGER_BOX,     // PEEP_ITEM_BURGER
    SHOP_ITEM_RUBBISH,              // PEEP_ITEM_CHIPS
    0xFF,                           // PEEP_ITEM_ICE_CREAM
    0xFF,                           // PEEP_ITEM_CANDYFLOSS
    0xFF,                           // PEEP_ITEM_EMPTY_CAN
    0xFF,                           // PEEP_ITEM_RUBBISH
    0xFF,                           // PEEP_ITEM_EMPTY_BURGER_BOX
    SHOP_ITEM_RUBBISH,              // PEEP_ITEM_PIZZA
    0xFF,                           // PEEP_ITEM_VOUCHER
    SHOP_ITEM_RUBBISH,              // PEEP_ITEM_POPCORN
    0xFF,                           // PEEP_ITEM_HOT_DOG
    0xFF,                           // PEEP_ITEM_TENTACLE
    0xFF,                           // PEEP_ITEM_HAT
    0xFF,                           // PEEP_ITEM_TOFFEE_APPLE
    0xFF,                           // PEEP_ITEM_TSHIRT
    0xFF,                           // PEEP_ITEM_DOUGHNUT
    SHOP_ITEM_EMPTY_CUP,            // PEEP_ITEM_COFFEE
    0xFF,                           // PEEP_ITEM_EMPTY_CUP
    SHOP_ITEM_EMPTY_BOX,            // PEEP_ITEM_CHICKEN
    SHOP_ITEM_EMPTY_BOTTLE,         // PEEP_ITEM_LEMONADE
    0xFF,                           // PEEP_ITEM_EMPTY_BOX
    0xFF,                           // PEEP_ITEM_EMPTY_BOTTLE
};

/** rct2: 0x00982342 */
static constexpr const uint8_t peep_extra_item_containers[] = {
    0xFF,                           // PEEP_ITEM_PHOTO2
    0xFF,                           // PEEP_ITEM_PHOTO3
    0xFF,                           // PEEP_ITEM_PHOTO4
    0xFF,                           // PEEP_ITEM_PRETZEL
    SHOP_ITEM_EMPTY_CUP,            // PEEP_ITEM_CHOCOLATE
    SHOP_ITEM_EMPTY_CUP,            // PEEP_ITEM_ICED_TEA
    0xFF,                           // PEEP_ITEM_FUNNEL_CAKE
    0xFF,                           // PEEP_ITEM_SUNGLASSES
    SHOP_ITEM_EMPTY_BOWL_BLUE,      // PEEP_ITEM_BEEF_NOODLES
    SHOP_ITEM_EMPTY_BOWL_BLUE,      // PEEP_ITEM_FRIED_RICE_NOODLES
    SHOP_ITEM_EMPTY_BOWL_RED,       // PEEP_ITEM_WONTON_SOUP
    SHOP_ITEM_EMPTY_BOWL_RED,       // PEEP_ITEM_MEATBALL_SOUP
    SHOP_ITEM_EMPTY_JUICE_CUP,      // PEEP_ITEM_FRUIT_JUICE
    SHOP_ITEM_EMPTY_DRINK_CARTON,   // PEEP_ITEM_SOYBEAN_MILK
    SHOP_ITEM_EMPTY_DRINK_CARTON,   // PEEP_ITEM_SU_JONGKWA
    0xFF,                           // PEEP_ITEM_SUB_SANDWICH
    0xFF,                           // PEEP_ITEM_COOKIE
    0xFF,                           // PEEP_ITEM_EMPTY_BOWL_RED
    0xFF,                           // PEEP_ITEM_EMPTY_DRINK_CARTON
    0xFF,                           // PEEP_ITEM_EMPTY_JUICE_CUP
    0xFF,                           // PEEP_ITEM_ROAST_SAUSAGE
    0xFF,                           // PEEP_ITEM_EMPTY_BOWL_BLUE
};

static constexpr const char *gPeepEasterEggNames[] = {
    "MICHAEL SCHUMACHER",
    "JACQUES VILLENEUVE",
    "DAMON HILL",
    "MR BEAN",
    "CHRIS SAWYER",
    "KATIE BRAYSHAW",
    "MELANIE WARN",
    "SIMON FOSTER",
    "JOHN WARDLEY",
    "LISA STIRLING",
    "DONALD MACRAE",
    "KATHERINE MCGOWAN",
    "FRANCES MCGOWAN",
    "CORINA MASSOURA",
    "CAROL YOUNG",
    "MIA SHERIDAN",
    "KATIE RODGER",
    "EMMA GARRELL",
    "JOANNE BARTON",
    "FELICITY ANDERSON",
    "KATIE SMITH",
    "EILIDH BELL",
    "NANCY STILLWAGON",
    "DAVID ELLIS"
};

// These arrays contain the base minimum and maximum nausea ratings for peeps, based on their nausea tolerance level.
static constexpr const ride_rating NauseaMinimumThresholds[] = {
    0, 0, 200, 400
};
// clang-format on

static bool peep_has_voucher_for_free_ride(Peep* peep, Ride* ride);
static void peep_ride_is_too_intense(Guest* peep, Ride* ride, bool peepAtRide);
static void peep_reset_ride_heading(Peep* peep);
static void peep_tried_to_enter_full_queue(Peep* peep, Ride* ride);
static int16_t peep_calculate_ride_satisfaction(Guest* peep, Ride* ride);
static void peep_update_favourite_ride(Peep* peep, Ride* ride);
static int16_t peep_calculate_ride_value_satisfaction(Peep* peep, Ride* ride);
static int16_t peep_calculate_ride_intensity_nausea_satisfaction(Peep* peep, Ride* ride);
static void peep_update_ride_nausea_growth(Peep* peep, Ride* ride);
static bool peep_should_go_on_ride_again(Peep* peep, Ride* ride);
static bool peep_should_preferred_intensity_increase(Peep* peep);
static bool peep_really_liked_ride(Peep* peep, Ride* ride);
static PeepThoughtType peep_assess_surroundings(int16_t centre_x, int16_t centre_y, int16_t centre_z);
static void peep_update_hunger(Peep* peep);
static void peep_decide_whether_to_leave_park(Peep* peep);
static void peep_leave_park(Peep* peep);
static void peep_head_for_nearest_ride_type(Guest* peep, int32_t rideType);
static void peep_head_for_nearest_ride_with_flags(Guest* peep, int32_t rideTypeFlags);
bool loc_690FD0(Peep* peep, uint8_t* rideToView, uint8_t* rideSeatToView, TileElement* tileElement);

bool Guest::GuestHasValidXY() const
{
    if (x != LOCATION_NULL)
    {
        if (x < (256 * 32) && y < (256 * 32))
        {
            return true;
        }
    }

    return false;
}

void Guest::ApplyEasterEggToNearbyGuests(easter_egg_function easter_egg)
{
    if (!GuestHasValidXY())
        return;

    uint16_t spriteIndex = sprite_get_first_in_quadrant(x, y);
    if (spriteIndex == SPRITE_INDEX_NULL)
        return;

    auto otherPeep = GET_PEEP(spriteIndex);
    for (; spriteIndex != SPRITE_INDEX_NULL; spriteIndex = otherPeep->next_in_quadrant)
    {
        otherPeep = GET_PEEP(spriteIndex);
        auto otherGuest = otherPeep->AsGuest();
        if (otherGuest)
        {
            auto zDiff = std::abs(otherPeep->z - z);
            if (zDiff <= 32)
            {
                (*this.*easter_egg)(otherGuest);
            }
        }
    }
}

void Guest::GivePassingPeepsPurpleClothes(Guest* passingPeep)
{
    passingPeep->tshirt_colour = COLOUR_BRIGHT_PURPLE;
    passingPeep->trousers_colour = COLOUR_BRIGHT_PURPLE;
    passingPeep->Invalidate();
}

void Guest::GivePassingPeepsPizza(Guest* passingPeep)
{
    if ((passingPeep->item_standard_flags & PEEP_ITEM_PIZZA))
        return;

    passingPeep->item_standard_flags |= PEEP_ITEM_PIZZA;

    int32_t peepDirection = (sprite_direction >> 3) ^ 2;
    int32_t otherPeepOppositeDirection = passingPeep->sprite_direction >> 3;
    if (peepDirection == otherPeepOppositeDirection)
    {
        if (passingPeep->action == PEEP_ACTION_NONE_1 || passingPeep->action == PEEP_ACTION_NONE_2)
        {
            passingPeep->action = PEEP_ACTION_WAVE_2;
            passingPeep->action_frame = 0;
            passingPeep->action_sprite_image_offset = 0;
            passingPeep->UpdateCurrentActionSpriteType();
        }
    }
}

void Guest::MakePassingPeepsSick(Guest* passingPeep)
{
    if (this == passingPeep)
        return;
    if (passingPeep->state != PEEP_STATE_WALKING)
        return;

    if (passingPeep->action == PEEP_ACTION_NONE_1 || passingPeep->action == PEEP_ACTION_NONE_2)
    {
        passingPeep->action = PEEP_ACTION_THROW_UP;
        passingPeep->action_frame = 0;
        passingPeep->action_sprite_image_offset = 0;
        passingPeep->UpdateCurrentActionSpriteType();
    }
}

void Guest::GivePassingPeepsIceCream(Guest* passingPeep)
{
    if (this == passingPeep)
        return;
    if (passingPeep->item_standard_flags & PEEP_ITEM_ICE_CREAM)
        return;

    passingPeep->item_standard_flags |= PEEP_ITEM_ICE_CREAM;
    passingPeep->UpdateSpriteType();
}

/**
 *
 *  rct2: 0x0068FD3A
 */
void Guest::UpdateEasterEggInteractions()
{
    if (peep_flags & PEEP_FLAGS_PURPLE)
    {
        ApplyEasterEggToNearbyGuests(&Guest::GivePassingPeepsPurpleClothes);
    }

    if (peep_flags & PEEP_FLAGS_PIZZA)
    {
        ApplyEasterEggToNearbyGuests(&Guest::GivePassingPeepsPizza);
    }

    if (peep_flags & PEEP_FLAGS_CONTAGIOUS)
    {
        ApplyEasterEggToNearbyGuests(&Guest::MakePassingPeepsSick);
    }

    if (peep_flags & PEEP_FLAGS_JOY)
    {
        if (scenario_rand() <= 1456)
        {
            if (action == PEEP_ACTION_NONE_1 || action == PEEP_ACTION_NONE_2)
            {
                action = PEEP_ACTION_JOY;
                action_frame = 0;
                action_sprite_image_offset = 0;
                UpdateCurrentActionSpriteType();
            }
        }
    }

    if (peep_flags & PEEP_FLAGS_ICE_CREAM)
    {
        ApplyEasterEggToNearbyGuests(&Guest::GivePassingPeepsIceCream);
    }
}

int32_t Guest::GetEasterEggNameId() const
{
    uint8_t args[32]{};
    char buffer[256]{};

    FormatNameTo(args);
    format_string(buffer, sizeof(buffer), STR_STRINGID, args);

    for (uint32_t i = 0; i < std::size(gPeepEasterEggNames); i++)
        if (_stricmp(buffer, gPeepEasterEggNames[i]) == 0)
            return static_cast<int32_t>(i);

    return -1;
}

void Guest::HandleEasterEggName()
{
    peep_flags &= ~PEEP_FLAGS_WAVING;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_KATIE_BRAYSHAW))
    {
        peep_flags |= PEEP_FLAGS_WAVING;
    }

    peep_flags &= ~PEEP_FLAGS_PHOTO;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_CHRIS_SAWYER))
    {
        peep_flags |= PEEP_FLAGS_PHOTO;
    }

    peep_flags &= ~PEEP_FLAGS_PAINTING;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_SIMON_FOSTER))
    {
        peep_flags |= PEEP_FLAGS_PAINTING;
    }

    peep_flags &= ~PEEP_FLAGS_WOW;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_JOHN_WARDLEY))
    {
        peep_flags |= PEEP_FLAGS_WOW;
    }

    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_MELANIE_WARN))
    {
        happiness = 250;
        happiness_target = 250;
        energy = 127;
        energy_target = 127;
        nausea = 0;
        nausea_target = 0;
    }

    peep_flags &= ~PEEP_FLAGS_LITTER;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_LISA_STIRLING))
    {
        peep_flags |= PEEP_FLAGS_LITTER;
    }

    peep_flags &= ~PEEP_FLAGS_LOST;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_DONALD_MACRAE))
    {
        peep_flags |= PEEP_FLAGS_LOST;
    }

    peep_flags &= ~PEEP_FLAGS_HUNGER;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_KATHERINE_MCGOWAN))
    {
        peep_flags |= PEEP_FLAGS_HUNGER;
    }

    peep_flags &= ~PEEP_FLAGS_BATHROOM;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_FRANCES_MCGOWAN))
    {
        peep_flags |= PEEP_FLAGS_BATHROOM;
    }

    peep_flags &= ~PEEP_FLAGS_CROWDED;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_CORINA_MASSOURA))
    {
        peep_flags |= PEEP_FLAGS_CROWDED;
    }

    peep_flags &= ~PEEP_FLAGS_HAPPINESS;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_CAROL_YOUNG))
    {
        peep_flags |= PEEP_FLAGS_HAPPINESS;
    }

    peep_flags &= ~PEEP_FLAGS_NAUSEA;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_MIA_SHERIDAN))
    {
        peep_flags |= PEEP_FLAGS_NAUSEA;
    }

    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_KATIE_RODGER))
    {
        peep_flags |= PEEP_FLAGS_LEAVING_PARK;
        peep_flags &= ~PEEP_FLAGS_PARK_ENTRANCE_CHOSEN;
    }

    peep_flags &= ~PEEP_FLAGS_PURPLE;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_EMMA_GARRELL))
    {
        peep_flags |= PEEP_FLAGS_PURPLE;
    }

    peep_flags &= ~PEEP_FLAGS_PIZZA;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_JOANNE_BARTON))
    {
        peep_flags |= PEEP_FLAGS_PIZZA;
    }

    peep_flags &= ~PEEP_FLAGS_CONTAGIOUS;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_FELICITY_ANDERSON))
    {
        peep_flags |= PEEP_FLAGS_CONTAGIOUS;
    }

    peep_flags &= ~PEEP_FLAGS_JOY;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_KATIE_SMITH))
    {
        peep_flags |= PEEP_FLAGS_JOY;
    }

    peep_flags &= ~PEEP_FLAGS_ANGRY;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_EILIDH_BELL))
    {
        peep_flags |= PEEP_FLAGS_ANGRY;
    }

    peep_flags &= ~PEEP_FLAGS_ICE_CREAM;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_NANCY_STILLWAGON))
    {
        peep_flags |= PEEP_FLAGS_ICE_CREAM;
    }

    peep_flags &= ~PEEP_FLAGS_HERE_WE_ARE;
    if (CheckEasterEggName(EASTEREGG_PEEP_NAME_DAVID_ELLIS))
    {
        peep_flags |= PEEP_FLAGS_HERE_WE_ARE;
    }
}

/**
 *
 *  rct2: 0x0069A5A0
 * tests if a peep's name matches a cheat code, normally returns using a register flag
 */
int32_t Guest::CheckEasterEggName(int32_t index) const
{
    uint8_t args[32]{};
    char buffer[256]{};

    FormatNameTo(args);
    format_string(buffer, sizeof(buffer), STR_STRINGID, args);

    return _stricmp(buffer, gPeepEasterEggNames[index]) == 0;
}

void Guest::Tick128UpdateGuest(int32_t index)
{
    if ((uint32_t)(index & 0x1FF) == (gCurrentTicks & 0x1FF))
    {
        /* Effect of masking with 0x1FF here vs mask 0x7F,
         * which is the condition for calling this function, is
         * to reduce how often the content in this conditional
         * is executed to once every four calls. */
        if (peep_flags & PEEP_FLAGS_CROWDED)
        {
            PeepThoughtType thought_type = crowded_thoughts[scenario_rand() & 0xF];
            if (thought_type != PEEP_THOUGHT_TYPE_NONE)
            {
                InsertNewThought(thought_type, PEEP_THOUGHT_ITEM_NONE);
            }
        }

        if (peep_flags & PEEP_FLAGS_EXPLODE && x != LOCATION_NULL)
        {
            if (state == PEEP_STATE_WALKING || state == PEEP_STATE_SITTING)
            {
                audio_play_sound_at_location(SoundId::Crash, { x, y, z });

                sprite_misc_explosion_cloud_create(x, y, z + 16);
                sprite_misc_explosion_flare_create(x, y, z + 16);

                Remove();
                return;
            }
            else
            {
                peep_flags &= ~PEEP_FLAGS_EXPLODE;
            }
        }

        if (peep_flags & PEEP_FLAGS_HUNGER)
        {
            if (hunger >= 15)
                hunger -= 15;
        }

        if (peep_flags & PEEP_FLAGS_BATHROOM)
        {
            if (toilet <= 180)
                toilet += 50;
        }

        if (peep_flags & PEEP_FLAGS_HAPPINESS)
        {
            happiness_target = 5;
        }

        if (peep_flags & PEEP_FLAGS_NAUSEA)
        {
            nausea_target = 200;
            if (nausea <= 130)
                nausea = 130;
        }

        if (angriness != 0)
            angriness--;

        if (state == PEEP_STATE_WALKING || state == PEEP_STATE_SITTING)
        {
            surroundings_thought_timeout++;
            if (surroundings_thought_timeout >= 18)
            {
                surroundings_thought_timeout = 0;
                if (x != LOCATION_NULL)
                {
                    PeepThoughtType thought_type = peep_assess_surroundings(x & 0xFFE0, y & 0xFFE0, z);

                    if (thought_type != PEEP_THOUGHT_TYPE_NONE)
                    {
                        InsertNewThought(thought_type, PEEP_THOUGHT_ITEM_NONE);
                        happiness_target = std::min(PEEP_MAX_HAPPINESS, happiness_target + 45);
                    }
                }
            }
        }

        UpdateSpriteType();

        if (state == PEEP_STATE_ON_RIDE || state == PEEP_STATE_ENTERING_RIDE)
        {
            time_on_ride = std::min(255, time_on_ride + 1);

            if (peep_flags & PEEP_FLAGS_WOW)
            {
                InsertNewThought(PEEP_THOUGHT_TYPE_WOW2, PEEP_THOUGHT_ITEM_NONE);
            }

            if (time_on_ride > 15)
            {
                happiness_target = std::max(0, happiness_target - 5);

                if (time_on_ride > 22)
                {
                    auto ride = get_ride(current_ride);
                    if (ride != nullptr)
                    {
                        PeepThoughtType thought_type = ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_IN_RIDE)
                            ? PEEP_THOUGHT_TYPE_GET_OUT
                            : PEEP_THOUGHT_TYPE_GET_OFF;

                        InsertNewThought(thought_type, current_ride);
                    }
                }
            }
        }

        if (state == PEEP_STATE_WALKING && outside_of_park == 0 && !(peep_flags & PEEP_FLAGS_LEAVING_PARK) && no_of_rides == 0
            && guest_heading_to_ride_id == RIDE_ID_NULL)
        {
            uint32_t time_duration = gScenarioTicks - time_in_park;
            time_duration /= 2048;

            if (time_duration >= 5)
            {
                PickRideToGoOn();

                if (guest_heading_to_ride_id == RIDE_ID_NULL)
                {
                    happiness_target = std::max(happiness_target - 128, 0);
                    peep_leave_park(this);
                    peep_update_hunger(this);
                    goto loc_68F9F3;
                }
            }
        }

        if ((scenario_rand() & 0xFFFF) <= ((item_standard_flags & PEEP_ITEM_MAP) ? 8192U : 2184U))
        {
            PickRideToGoOn();
        }

        if ((uint32_t)(index & 0x3FF) == (gCurrentTicks & 0x3FF))
        {
            /* Effect of masking with 0x3FF here vs mask 0x1FF,
             * which is used in the encompassing conditional, is
             * to reduce how often the content in this conditional
             * is executed to once every second time the encompassing
             * conditional executes. */

            if (outside_of_park == 0 && (state == PEEP_STATE_WALKING || state == PEEP_STATE_SITTING))
            {
                uint8_t num_thoughts = 0;
                PeepThoughtType possible_thoughts[5];

                if (peep_flags & PEEP_FLAGS_LEAVING_PARK)
                {
                    possible_thoughts[num_thoughts++] = PEEP_THOUGHT_TYPE_GO_HOME;
                }
                else
                {
                    if (energy <= 70 && happiness < 128)
                    {
                        possible_thoughts[num_thoughts++] = PEEP_THOUGHT_TYPE_TIRED;
                    }

                    if (hunger <= 10 && !HasFood())
                    {
                        possible_thoughts[num_thoughts++] = PEEP_THOUGHT_TYPE_HUNGRY;
                    }

                    if (thirst <= 25 && !HasFood())
                    {
                        possible_thoughts[num_thoughts++] = PEEP_THOUGHT_TYPE_THIRSTY;
                    }

                    if (toilet >= 160)
                    {
                        possible_thoughts[num_thoughts++] = PEEP_THOUGHT_TYPE_BATHROOM;
                    }

                    if (!(gParkFlags & PARK_FLAGS_NO_MONEY) && cash_in_pocket <= MONEY(9, 00) && happiness >= 105
                        && energy >= 70)
                    {
                        /* The energy check was originally a second check on happiness.
                         * This was superfluous so should probably check something else.
                         * Guessed that this should really be checking energy, since
                         * the addresses for happiness and energy are quite close,
                         * 70 is also the threshold for tired thoughts (see above) and
                         * it makes sense that a tired peep might not think about getting
                         * more money. */
                        possible_thoughts[num_thoughts++] = PEEP_THOUGHT_TYPE_RUNNING_OUT;
                    }
                }

                if (num_thoughts != 0)
                {
                    PeepThoughtType chosen_thought = possible_thoughts[scenario_rand() % num_thoughts];

                    InsertNewThought(chosen_thought, PEEP_THOUGHT_ITEM_NONE);

                    switch (chosen_thought)
                    {
                        case PEEP_THOUGHT_TYPE_HUNGRY:
                            peep_head_for_nearest_ride_with_flags(this, RIDE_TYPE_FLAG_SELLS_FOOD);
                            break;
                        case PEEP_THOUGHT_TYPE_THIRSTY:
                            peep_head_for_nearest_ride_with_flags(this, RIDE_TYPE_FLAG_SELLS_DRINKS);
                            break;
                        case PEEP_THOUGHT_TYPE_BATHROOM:
                            peep_head_for_nearest_ride_with_flags(this, RIDE_TYPE_FLAG_IS_BATHROOM);
                            break;
                        case PEEP_THOUGHT_TYPE_RUNNING_OUT:
                            peep_head_for_nearest_ride_type(this, RIDE_TYPE_CASH_MACHINE);
                            break;
                        default:
                            break;
                    }
                }
            }
        }
        else
        {
            /* This branch of the conditional is executed on the
             * remaining times the encompassing conditional is
             * executed (which is also every second time, but
             * the alternate time to the true branch). */
            if (nausea >= 140)
            {
                PeepThoughtType thought_type = PEEP_THOUGHT_TYPE_SICK;
                if (nausea >= 200)
                {
                    thought_type = PEEP_THOUGHT_TYPE_VERY_SICK;
                    peep_head_for_nearest_ride_type(this, RIDE_TYPE_FIRST_AID);
                }
                InsertNewThought(thought_type, PEEP_THOUGHT_ITEM_NONE);
            }
        }

        switch (state)
        {
            case PEEP_STATE_WALKING:
            case PEEP_STATE_LEAVING_PARK:
            case PEEP_STATE_ENTERING_PARK:
                peep_decide_whether_to_leave_park(this);
                peep_update_hunger(this);
                break;

            case PEEP_STATE_SITTING:
                if (energy_target <= 135)
                    energy_target += 5;

                if (thirst >= 5)
                {
                    thirst -= 4;
                    toilet = std::min(255, toilet + 3);
                }

                if (nausea_target >= 50)
                    nausea_target -= 6;

                // In the original this branched differently
                // but it would mean setting the peep happiness from
                // a thought type entry which i think is incorrect.
                peep_update_hunger(this);
                break;

            case PEEP_STATE_QUEUING:
                if (time_in_queue >= 2000)
                {
                    /* Peep happiness is affected once the peep has been waiting
                     * too long in a queue. */
                    TileElement* tileElement = map_get_first_element_at(next_x / 32, next_y / 32);
                    bool found = false;
                    do
                    {
                        if (tileElement == nullptr)
                            break;
                        if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
                            continue;
                        if (tileElement->base_height != next_z)
                            continue;

                        // Check if the footpath has a queue line TV monitor on it
                        if (tileElement->AsPath()->HasAddition() && !tileElement->AsPath()->AdditionIsGhost())
                        {
                            uint8_t pathSceneryIndex = tileElement->AsPath()->GetAdditionEntryIndex();
                            rct_scenery_entry* sceneryEntry = get_footpath_item_entry(pathSceneryIndex);
                            if (sceneryEntry != nullptr && sceneryEntry->path_bit.flags & PATH_BIT_FLAG_IS_QUEUE_SCREEN)
                            {
                                found = true;
                            }
                        }
                        break;
                    } while (!(tileElement++)->IsLastForTile());

                    if (found)
                    {
                        /* Queue line TV monitors make the peeps waiting in the queue
                         * slowly happier, up to a certain level. */
                        /* Why don't queue line TV monitors start affecting the peeps
                         * as soon as they join the queue?? */
                        if (happiness_target < 90)
                            happiness_target = 90;

                        if (happiness_target < 165)
                            happiness_target += 2;
                    }
                    else
                    {
                        /* Without a queue line TV monitor peeps waiting too long
                         * in a queue get less happy. */
                        happiness_target = std::max(happiness_target - 4, 0);
                    }
                }
                peep_update_hunger(this);
                break;
            case PEEP_STATE_ENTERING_RIDE:
                if (sub_state == 17 || sub_state == 15)
                {
                    peep_decide_whether_to_leave_park(this);
                }
                peep_update_hunger(this);
                break;
            default:
                break;
        }

    loc_68F9F3:
        // Idle peep happiness tends towards 127 (50%).
        if (happiness_target >= 128)
            happiness_target--;
        else
            happiness_target++;

        nausea_target = std::max(nausea_target - 2, 0);

        if (energy <= 50)
        {
            energy = std::max(energy - 2, 0);
        }

        if (hunger < 10)
        {
            hunger = std::max(hunger - 1, 0);
        }

        if (thirst < 10)
        {
            thirst = std::max(thirst - 1, 0);
        }

        if (toilet >= 195)
        {
            toilet--;
        }

        if (state == PEEP_STATE_WALKING && nausea_target >= 128)
        {
            if ((scenario_rand() & 0xFF) <= (uint8_t)((nausea - 128) / 2))
            {
                if (action >= PEEP_ACTION_NONE_1)
                {
                    action = PEEP_ACTION_THROW_UP;
                    action_frame = 0;
                    action_sprite_image_offset = 0;
                    UpdateCurrentActionSpriteType();
                }
            }
        }
    }

    // Remaining content is executed every call.

    // 68FA89
    if (time_to_consume == 0 && HasFood())
    {
        time_to_consume += 3;
    }

    if (time_to_consume != 0 && state != PEEP_STATE_ON_RIDE)
    {
        time_to_consume = std::max(time_to_consume - 3, 0);

        if (HasDrink())
        {
            thirst = std::min(thirst + 7, 255);
        }
        else
        {
            hunger = std::min(hunger + 7, 255);
            thirst = std::max(thirst - 3, 0);
            toilet = std::min(toilet + 2, 255);
        }

        if (time_to_consume == 0)
        {
            int32_t chosen_food = bitscanforward(HasFoodStandardFlag());
            if (chosen_food != -1)
            {
                item_standard_flags &= ~(1 << chosen_food);

                uint8_t discard_container = peep_item_containers[chosen_food];
                if (discard_container != 0xFF)
                {
                    item_standard_flags |= (1 << discard_container);
                }

                window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY;
                UpdateSpriteType();
            }
            else
            {
                chosen_food = bitscanforward(HasFoodExtraFlag());
                if (chosen_food != -1)
                {
                    item_extra_flags &= ~(1 << chosen_food);
                    uint8_t discard_container = peep_extra_item_containers[chosen_food];
                    if (discard_container != 0xFF)
                    {
                        if (discard_container >= 32)
                            item_extra_flags |= (1 << (discard_container - 32));
                        else
                            item_standard_flags |= (1 << discard_container);
                    }

                    window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY;
                    UpdateSpriteType();
                }
            }
        }
    }

    uint8_t newEnergy = energy;
    uint8_t newTargetEnergy = energy_target;
    if (newEnergy >= newTargetEnergy)
    {
        newEnergy -= 2;
        if (newEnergy < newTargetEnergy)
            newEnergy = newTargetEnergy;
    }
    else
    {
        newEnergy = std::min(PEEP_MAX_ENERGY_TARGET, newEnergy + 4);
        if (newEnergy > newTargetEnergy)
            newEnergy = newTargetEnergy;
    }

    if (newEnergy < PEEP_MIN_ENERGY)
        newEnergy = PEEP_MIN_ENERGY;

    /* Previous code here suggested maximum energy is 128. */
    newEnergy = std::min(static_cast<uint8_t>(PEEP_MAX_ENERGY), newEnergy);

    if (newEnergy != energy)
    {
        energy = newEnergy;
        window_invalidate_flags |= PEEP_INVALIDATE_PEEP_2;
    }

    uint8_t newHappiness = happiness;
    uint8_t newHappinessGrowth = happiness_target;
    if (newHappiness >= newHappinessGrowth)
    {
        newHappiness = std::max(newHappiness - 4, 0);
        if (newHappiness < newHappinessGrowth)
            newHappiness = newHappinessGrowth;
    }
    else
    {
        newHappiness = std::min(255, newHappiness + 4);
        if (newHappiness > newHappinessGrowth)
            newHappiness = newHappinessGrowth;
    }

    if (newHappiness != happiness)
    {
        happiness = newHappiness;
        window_invalidate_flags |= PEEP_INVALIDATE_PEEP_2;
    }

    uint8_t newNausea = nausea;
    uint8_t newNauseaGrowth = nausea_target;
    if (newNausea >= newNauseaGrowth)
    {
        newNausea = std::max(newNausea - 4, 0);
        if (newNausea < newNauseaGrowth)
            newNausea = newNauseaGrowth;
    }
    else
    {
        newNausea = std::min(255, newNausea + 4);
        if (newNausea > newNauseaGrowth)
            newNausea = newNauseaGrowth;
    }

    if (newNausea != nausea)
    {
        nausea = newNausea;
        window_invalidate_flags |= PEEP_INVALIDATE_PEEP_2;
    }
}

/**
 *
 *  rct2: 0x00691677
 */
void Guest::TryGetUpFromSitting()
{
    // Eats all food first
    if (HasFood())
        return;

    time_to_sitdown--;
    if (time_to_sitdown)
        return;

    SetState(PEEP_STATE_WALKING);

    // Set destination to the centre of the tile.
    destination_x = (x & 0xFFE0) + 16;
    destination_y = (y & 0xFFE0) + 16;
    destination_tolerance = 5;
    UpdateCurrentActionSpriteType();
}

/**
 *
 *  rct2: 0x0069152B
 */
void Guest::UpdateSitting()
{
    if (sub_state == PEEP_SITTING_TRYING_TO_SIT)
    {
        if (!CheckForPath())
            return;
        // 691541

        uint8_t pathingResult;
        PerformNextAction(pathingResult);
        if (!(pathingResult & PATHING_DESTINATION_REACHED))
            return;

        LocationXYZ16 loc = {
            (int16_t)((x & 0xFFE0) + BenchUseOffsets[var_37 & 0x7].x),
            (int16_t)((y & 0xFFE0) + BenchUseOffsets[var_37 & 0x7].y),
            z,
        };

        MoveTo(loc.x, loc.y, loc.z);

        sprite_direction = ((var_37 + 2) & 3) * 8;
        action = PEEP_ACTION_NONE_1;
        next_action_sprite_type = PEEP_ACTION_SPRITE_TYPE_SITTING_IDLE;
        SwitchNextActionSpriteType();

        sub_state = PEEP_SITTING_SAT_DOWN;

        // Sets time to sit on seat
        time_to_sitdown = (129 - energy) * 16 + 50;
    }
    else if (sub_state == PEEP_SITTING_SAT_DOWN)
    {
        if (action < PEEP_ACTION_NONE_1)
        {
            UpdateAction();
            if (action != PEEP_ACTION_NONE_2)
                return;

            action = PEEP_ACTION_NONE_1;
            TryGetUpFromSitting();
            return;
        }

        if ((peep_flags & PEEP_FLAGS_LEAVING_PARK))
        {
            SetState(PEEP_STATE_WALKING);

            // Set destination to the centre of the tile
            destination_x = (x & 0xFFE0) + 16;
            destination_y = (y & 0xFFE0) + 16;
            destination_tolerance = 5;
            UpdateCurrentActionSpriteType();
            return;
        }

        if (sprite_type == PEEP_SPRITE_TYPE_UMBRELLA)
        {
            TryGetUpFromSitting();
            return;
        }

        if (HasFood())
        {
            if ((scenario_rand() & 0xFFFF) > 1310)
            {
                TryGetUpFromSitting();
                return;
            }
            action = PEEP_ACTION_SITTING_EAT_FOOD;
            action_frame = 0;
            action_sprite_image_offset = 0;
            UpdateCurrentActionSpriteType();
            return;
        }

        int32_t rand = scenario_rand();
        if ((rand & 0xFFFF) > 131)
        {
            TryGetUpFromSitting();
            return;
        }
        if (sprite_type == PEEP_SPRITE_TYPE_BALLOON || sprite_type == PEEP_SPRITE_TYPE_HAT)
        {
            TryGetUpFromSitting();
            return;
        }

        action = PEEP_ACTION_SITTING_LOOK_AROUND_LEFT;
        if (rand & 0x80000000)
        {
            action = PEEP_ACTION_SITTING_LOOK_AROUND_RIGHT;
        }

        if (rand & 0x40000000)
        {
            action = PEEP_ACTION_SITTING_CHECK_WATCH;
        }
        action_frame = 0;
        action_sprite_image_offset = 0;
        UpdateCurrentActionSpriteType();
        return;
    }
}

bool Guest::HasItem(int32_t peepItem) const
{
    if (peepItem < 32)
    {
        return item_standard_flags & (1u << peepItem);
    }
    else
    {
        return item_extra_flags & (1u << (peepItem - 32));
    }
}

int32_t Guest::HasFoodStandardFlag() const
{
    return item_standard_flags
        & (PEEP_ITEM_DRINK | PEEP_ITEM_BURGER | PEEP_ITEM_CHIPS | PEEP_ITEM_ICE_CREAM | PEEP_ITEM_CANDYFLOSS | PEEP_ITEM_PIZZA
           | PEEP_ITEM_POPCORN | PEEP_ITEM_HOT_DOG | PEEP_ITEM_TENTACLE | PEEP_ITEM_TOFFEE_APPLE | PEEP_ITEM_DOUGHNUT
           | PEEP_ITEM_COFFEE | PEEP_ITEM_CHICKEN | PEEP_ITEM_LEMONADE);
}

int32_t Guest::HasFoodExtraFlag() const
{
    return item_extra_flags
        & (PEEP_ITEM_PRETZEL | PEEP_ITEM_CHOCOLATE | PEEP_ITEM_ICED_TEA | PEEP_ITEM_FUNNEL_CAKE | PEEP_ITEM_BEEF_NOODLES
           | PEEP_ITEM_FRIED_RICE_NOODLES | PEEP_ITEM_WONTON_SOUP | PEEP_ITEM_MEATBALL_SOUP | PEEP_ITEM_FRUIT_JUICE
           | PEEP_ITEM_SOYBEAN_MILK | PEEP_ITEM_SU_JONGKWA | PEEP_ITEM_SUB_SANDWICH | PEEP_ITEM_COOKIE
           | PEEP_ITEM_ROAST_SAUSAGE);
}

bool Guest::HasDrinkStandardFlag() const
{
    return item_standard_flags & (PEEP_ITEM_DRINK | PEEP_ITEM_COFFEE | PEEP_ITEM_LEMONADE);
}

bool Guest::HasDrinkExtraFlag() const
{
    return item_extra_flags
        & (PEEP_ITEM_CHOCOLATE | PEEP_ITEM_ICED_TEA | PEEP_ITEM_FRUIT_JUICE | PEEP_ITEM_SOYBEAN_MILK | PEEP_ITEM_SU_JONGKWA);
}

/**
 * To simplify check of NOT(0x12BA3C0 and 0x118F48)
 * returns 0 on no food.
 */
bool Guest::HasDrink() const
{
    return HasDrinkStandardFlag() || HasDrinkExtraFlag();
}

int32_t Guest::HasEmptyContainerStandardFlag() const
{
    return item_standard_flags
        & (PEEP_ITEM_EMPTY_CAN | PEEP_ITEM_EMPTY_BURGER_BOX | PEEP_ITEM_EMPTY_CUP | PEEP_ITEM_RUBBISH | PEEP_ITEM_EMPTY_BOX
           | PEEP_ITEM_EMPTY_BOTTLE);
}

int32_t Guest::HasEmptyContainerExtraFlag() const
{
    return item_extra_flags
        & (PEEP_ITEM_EMPTY_BOWL_RED | PEEP_ITEM_EMPTY_DRINK_CARTON | PEEP_ITEM_EMPTY_JUICE_CUP | PEEP_ITEM_EMPTY_BOWL_BLUE);
}

bool Guest::HasEmptyContainer() const
{
    return HasEmptyContainerStandardFlag() || HasEmptyContainerExtraFlag();
}

/**
 *
 *  rct2: 0x69C308
 * Check if lost.
 */
void Guest::CheckIfLost()
{
    if (!(peep_flags & PEEP_FLAGS_LOST))
    {
        if (ride_get_count() < 2)
            return;
        peep_flags ^= PEEP_FLAGS_21;

        if (!(peep_flags & PEEP_FLAGS_21))
            return;

        time_lost++;
        if (time_lost != 254)
            return;
        time_lost = 230;
    }
    InsertNewThought(PEEP_THOUGHT_TYPE_LOST, PEEP_THOUGHT_ITEM_NONE);

    happiness_target = std::max(happiness_target - 30, 0);
}

/**
 *
 *  rct2: 0x69C26B
 * Check if cant find ride.
 */
void Guest::CheckCantFindRide()
{
    if (guest_heading_to_ride_id == RIDE_ID_NULL)
        return;

    // Peeps will think "I can't find ride X" twice before giving up completely.
    if (peep_is_lost_countdown == 30 || peep_is_lost_countdown == 60)
    {
        InsertNewThought(PEEP_THOUGHT_TYPE_CANT_FIND, guest_heading_to_ride_id);
        happiness_target = std::max(happiness_target - 30, 0);
    }

    peep_is_lost_countdown--;
    if (peep_is_lost_countdown != 0)
        return;

    guest_heading_to_ride_id = RIDE_ID_NULL;
    rct_window* w = window_find_by_number(WC_PEEP, sprite_index);

    if (w)
    {
        window_event_invalidate_call(w);
    }

    window_invalidate_by_number(WC_PEEP, sprite_index);
}

/**
 *
 *  rct2: 0x69C2D0
 * Check if cant find exit.
 */
void Guest::CheckCantFindExit()
{
    if (!(peep_flags & PEEP_FLAGS_LEAVING_PARK))
        return;

    // Peeps who can't find the park exit will continue to get less happy until they find it.
    if (peep_is_lost_countdown == 1)
    {
        InsertNewThought(PEEP_THOUGHT_TYPE_CANT_FIND_EXIT, PEEP_THOUGHT_ITEM_NONE);
        happiness_target = std::max(happiness_target - 30, 0);
    }

    if (--peep_is_lost_countdown == 0)
        peep_is_lost_countdown = 90;
}

/** Main logic to decide whether a peep should buy an item in question
 *
 * Also handles the purchase as well, so once it returns, the peep will have the
 * item and the money will have been deducted.
 *
 * eax: shopItem | (rideIndex << 8)
 * ecx: price
 * esi: *peep
 *
 * Returns 0 or 1 depending on if the peep decided to buy the item
 *
 *  rct2: 0x0069AF1E
 */
bool Guest::DecideAndBuyItem(Ride* ride, int32_t shopItem, money32 price)
{
    money32 itemValue;

    bool hasVoucher = false;

    if ((item_standard_flags & PEEP_ITEM_VOUCHER) && (voucher_type == VOUCHER_TYPE_FOOD_OR_DRINK_FREE)
        && (voucher_arguments == shopItem))
    {
        hasVoucher = true;
    }

    if (HasItem(shopItem))
    {
        InsertNewThought(PEEP_THOUGHT_TYPE_ALREADY_GOT, shopItem);
        return false;
    }

    if (shop_item_is_food_or_drink(shopItem))
    {
        int32_t food = -1;
        if ((food = HasFoodStandardFlag()) != 0)
        {
            InsertNewThought(PEEP_THOUGHT_TYPE_HAVENT_FINISHED, bitscanforward(food));
            return false;
        }
        else if ((food = HasFoodExtraFlag()) != 0)
        {
            InsertNewThought(PEEP_THOUGHT_TYPE_HAVENT_FINISHED, bitscanforward(food) + 32);
            return false;
        }
        else if (nausea >= 145)
            return false;
    }

    if ((shopItem == SHOP_ITEM_BALLOON) || (shopItem == SHOP_ITEM_ICE_CREAM) || (shopItem == SHOP_ITEM_CANDYFLOSS)
        || (shopItem == SHOP_ITEM_SUNGLASSES))
    {
        if (climate_is_raining())
            return false;
    }

    if ((shopItem == SHOP_ITEM_SUNGLASSES) || (shopItem == SHOP_ITEM_ICE_CREAM))
    {
        if (gClimateCurrent.Temperature < 12)
            return false;
    }

    if (shop_item_is_food(shopItem) && (hunger > 75))
    {
        InsertNewThought(PEEP_THOUGHT_TYPE_NOT_HUNGRY, PEEP_THOUGHT_ITEM_NONE);
        return false;
    }

    if (shop_item_is_drink(shopItem) && (thirst > 75))
    {
        InsertNewThought(PEEP_THOUGHT_TYPE_NOT_THIRSTY, PEEP_THOUGHT_ITEM_NONE);
        return false;
    }

    if (shopItem == SHOP_ITEM_UMBRELLA && climate_is_raining())
        goto loc_69B119;

    if ((shopItem != SHOP_ITEM_MAP) && shop_item_is_souvenir(shopItem) && !hasVoucher)
    {
        if (((scenario_rand() & 0x7F) + 0x73) > happiness)
            return false;
        else if (no_of_rides < 3)
            return false;
    }

loc_69B119:
    if (!hasVoucher)
    {
        if (price != 0 && !(gParkFlags & PARK_FLAGS_NO_MONEY))
        {
            if (cash_in_pocket == 0)
            {
                InsertNewThought(PEEP_THOUGHT_TYPE_SPENT_MONEY, PEEP_THOUGHT_ITEM_NONE);
                return false;
            }
            if (price > cash_in_pocket)
            {
                InsertNewThought(PEEP_THOUGHT_TYPE_CANT_AFFORD, shopItem);
                return false;
            }
        }

        if (gClimateCurrent.Temperature >= 21)
            itemValue = ShopItems[shopItem].HotValue;
        else if (gClimateCurrent.Temperature <= 11)
            itemValue = ShopItems[shopItem].ColdValue;
        else
            itemValue = ShopItems[shopItem].BaseValue;

        if (itemValue < price)
        {
            itemValue -= price;
            if (shopItem == SHOP_ITEM_UMBRELLA)
            {
                if (climate_is_raining())
                    goto loc_69B221;
            }

            itemValue = -itemValue;
            if (happiness >= 128)
                itemValue /= 2;

            if (happiness >= 180)
                itemValue /= 2;

            if (itemValue > ((money16)(scenario_rand() & 0x07)))
            {
                // "I'm not paying that much for x"
                PeepThoughtType thought_type = static_cast<PeepThoughtType>(
                    (shopItem >= 32 ? (PEEP_THOUGHT_TYPE_PHOTO2_MUCH + (shopItem - 32))
                                    : (PEEP_THOUGHT_TYPE_BALLOON_MUCH + shopItem)));
                InsertNewThought(thought_type, ride->id);
                return false;
            }
        }
        else
        {
            itemValue -= price;
            itemValue = std::max(8, itemValue);

            if (!(gParkFlags & PARK_FLAGS_NO_MONEY))
            {
                if (itemValue >= (money32)(scenario_rand() & 0x07))
                {
                    // "This x is a really good value"
                    PeepThoughtType thought_item = static_cast<PeepThoughtType>(
                        (shopItem >= 32 ? (PEEP_THOUGHT_TYPE_PHOTO2 + (shopItem - 32))
                                        : (PEEP_THOUGHT_TYPE_BALLOON + shopItem)));
                    InsertNewThought(thought_item, ride->id);
                }
            }

            int32_t happinessGrowth = itemValue * 4;
            happiness_target = std::min((happiness_target + happinessGrowth), PEEP_MAX_HAPPINESS);
            happiness = std::min((happiness + happinessGrowth), PEEP_MAX_HAPPINESS);
        }
    }

loc_69B221:
    if (!hasVoucher)
    {
        if (gClimateCurrent.Temperature >= 21)
            itemValue = ShopItems[shopItem].HotValue;
        else if (gClimateCurrent.Temperature <= 11)
            itemValue = ShopItems[shopItem].ColdValue;
        else
            itemValue = ShopItems[shopItem].BaseValue;

        itemValue -= price;
        uint8_t satisfaction = 0;
        if (itemValue > -8)
        {
            satisfaction++;
            if (itemValue > -3)
            {
                satisfaction++;
                if (itemValue > 3)
                    satisfaction++;
            }
        }

        ride_update_satisfaction(ride, satisfaction);
    }

    // The peep has now decided to buy the item (or, specifically, has not been
    // dissuaded so far).
    if (shopItem >= 32)
        item_extra_flags |= (1u << (shopItem - 32));
    else
        item_standard_flags |= (1u << shopItem);

    if (shopItem == SHOP_ITEM_TSHIRT)
        tshirt_colour = ride->track_colour[0].main;

    if (shopItem == SHOP_ITEM_HAT)
        hat_colour = ride->track_colour[0].main;

    if (shopItem == SHOP_ITEM_BALLOON)
        balloon_colour = ride->track_colour[0].main;

    if (shopItem == SHOP_ITEM_UMBRELLA)
        umbrella_colour = ride->track_colour[0].main;

    if (shopItem == SHOP_ITEM_MAP)
        peep_reset_pathfind_goal(this);

    uint16_t consumptionTime = item_consumption_time[shopItem];
    time_to_consume = std::min((time_to_consume + consumptionTime), 255);

    if (shopItem == SHOP_ITEM_PHOTO)
        photo1_ride_ref = ride->id;

    if (shopItem == SHOP_ITEM_PHOTO2)
        photo2_ride_ref = ride->id;

    if (shopItem == SHOP_ITEM_PHOTO3)
        photo3_ride_ref = ride->id;

    if (shopItem == SHOP_ITEM_PHOTO4)
        photo4_ride_ref = ride->id;

    window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY;
    UpdateSpriteType();
    if (peep_flags & PEEP_FLAGS_TRACKING)
    {
        auto nameArgLen = FormatNameTo(gCommonFormatArgs);
        set_format_arg(nameArgLen, rct_string_id, ShopItems[shopItem].Naming.Indefinite);
        if (gConfigNotifications.guest_bought_item)
        {
            news_item_add_to_queue(2, STR_PEEP_TRACKING_NOTIFICATION_BOUGHT_X, sprite_index);
        }
    }

    if (shop_item_is_food(shopItem))
        no_of_food++;

    if (shop_item_is_drink(shopItem))
        no_of_drinks++;

    if (shop_item_is_souvenir(shopItem))
        no_of_souvenirs++;

    money16* expend_type = &paid_on_souvenirs;
    gCommandExpenditureType = RCT_EXPENDITURE_TYPE_SHOP_STOCK;

    if (shop_item_is_food(shopItem))
    {
        expend_type = &paid_on_food;
        gCommandExpenditureType = RCT_EXPENDITURE_TYPE_FOODDRINK_STOCK;
    }

    if (shop_item_is_drink(shopItem))
    {
        expend_type = &paid_on_drink;
        gCommandExpenditureType = RCT_EXPENDITURE_TYPE_FOODDRINK_STOCK;
    }

    if (!(gParkFlags & PARK_FLAGS_NO_MONEY))
        finance_payment(ShopItems[shopItem].Cost, gCommandExpenditureType);

    // Sets the expenditure type to *_FOODDRINK_SALES or *_SHOP_SALES appropriately.
    gCommandExpenditureType--;
    if (hasVoucher)
    {
        item_standard_flags &= ~PEEP_ITEM_VOUCHER;
        window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY;
    }
    else if (!(gParkFlags & PARK_FLAGS_NO_MONEY))
    {
        SpendMoney(*expend_type, price);
    }
    ride->total_profit += (price - ShopItems[shopItem].Cost);
    ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_INCOME;
    ride->cur_num_customers++;
    ride->total_customers++;
    ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;

    return true;
}

/**
 * Updates various peep stats upon entering a ride, as well as updating the
 * ride's satisfaction value.
 *  rct2: 0x0069545B
 */
void Guest::OnEnterRide(ride_id_t rideIndex)
{
    auto ride = get_ride(rideIndex);
    if (ride == nullptr)
        return;

    // Calculate how satisfying the ride is for the peep. Can range from -140 to +105.
    int16_t satisfaction = peep_calculate_ride_satisfaction(this, ride);

    // Update the satisfaction stat of the ride.
    uint8_t rideSatisfaction = 0;
    if (satisfaction >= 40)
        rideSatisfaction = 3;
    else if (satisfaction >= 20)
        rideSatisfaction = 2;
    else if (satisfaction >= 0)
        rideSatisfaction = 1;

    ride_update_satisfaction(ride, rideSatisfaction);

    // Update various peep stats.
    if (no_of_rides < 255)
        no_of_rides++;

    SetHasRidden(ride);
    peep_update_favourite_ride(this, ride);
    happiness_target = std::clamp(happiness_target + satisfaction, 0, PEEP_MAX_HAPPINESS);
    peep_update_ride_nausea_growth(this, ride);
}

/**
 *
 *  rct2: 0x0069576E
 */
void Guest::OnExitRide(ride_id_t rideIndex)
{
    auto ride = get_ride(rideIndex);
    if (peep_flags & PEEP_FLAGS_RIDE_SHOULD_BE_MARKED_AS_FAVOURITE)
    {
        peep_flags &= ~PEEP_FLAGS_RIDE_SHOULD_BE_MARKED_AS_FAVOURITE;
        favourite_ride = rideIndex;
        // TODO fix this flag name or add another one
        window_invalidate_flags |= PEEP_INVALIDATE_STAFF_STATS;
    }
    happiness = happiness_target;
    nausea = nausea_target;
    window_invalidate_flags |= PEEP_INVALIDATE_PEEP_STATS;

    if (peep_flags & PEEP_FLAGS_LEAVING_PARK)
        peep_flags &= ~(PEEP_FLAGS_PARK_ENTRANCE_CHOSEN);

    if (ride != nullptr && peep_should_go_on_ride_again(this, ride))
    {
        guest_heading_to_ride_id = rideIndex;
        peep_is_lost_countdown = 200;
        peep_reset_pathfind_goal(this);
        window_invalidate_flags |= PEEP_INVALIDATE_PEEP_ACTION;
    }

    if (peep_should_preferred_intensity_increase(this))
    {
        if (intensity <= 255 - 16)
        {
            intensity += 16;
        }
    }

    if (ride != nullptr && peep_really_liked_ride(this, ride))
    {
        InsertNewThought(PEEP_THOUGHT_TYPE_WAS_GREAT, rideIndex);

        SoundId laughs[3] = { SoundId::Laugh1, SoundId::Laugh2, SoundId::Laugh3 };
        int32_t laughType = scenario_rand() & 7;
        if (laughType < 3)
        {
            audio_play_sound_at_location(laughs[laughType], { x, y, z });
        }
    }

    if (ride != nullptr)
    {
        ride->total_customers++;
        ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;
    }
}

/**
 * To simplify check of 0x36BA3E0 and 0x11FF78
 * returns false on no food.
 */
bool Guest::HasFood() const
{
    return HasFoodStandardFlag() || HasFoodExtraFlag();
}

/**
 *
 *  rct2: 0x00695DD2
 */
void Guest::PickRideToGoOn()
{
    if (state != PEEP_STATE_WALKING)
        return;
    if (guest_heading_to_ride_id != RIDE_ID_NULL)
        return;
    if (peep_flags & PEEP_FLAGS_LEAVING_PARK)
        return;
    if (HasFood())
        return;
    if (x == LOCATION_NULL)
        return;

    auto ride = FindBestRideToGoOn();
    if (ride != nullptr)
    {
        // Head to that ride
        guest_heading_to_ride_id = ride->id;
        peep_is_lost_countdown = 200;
        peep_reset_pathfind_goal(this);
        window_invalidate_flags |= PEEP_INVALIDATE_PEEP_ACTION;

        // Make peep look at their map if they have one
        if (item_standard_flags & PEEP_ITEM_MAP)
        {
            ReadMap();
        }
    }
}

Ride* Guest::FindBestRideToGoOn()
{
    // Pick the most exciting ride
    auto rideConsideration = FindRidesToGoOn();
    Ride* mostExcitingRide = nullptr;
    for (auto& ride : GetRideManager())
    {
        if (rideConsideration.size() > ride.id && rideConsideration[ride.id])
        {
            if (!(ride.lifecycle_flags & RIDE_LIFECYCLE_QUEUE_FULL))
            {
                if (ShouldGoOnRide(&ride, 0, false, true) && ride_has_ratings(&ride))
                {
                    if (mostExcitingRide == nullptr || ride.excitement > mostExcitingRide->excitement)
                    {
                        mostExcitingRide = &ride;
                    }
                }
            }
        }
    }
    return mostExcitingRide;
}

std::bitset<MAX_RIDES> Guest::FindRidesToGoOn()
{
    std::bitset<MAX_RIDES> rideConsideration;

    // FIX  Originally checked for a toy, likely a mistake and should be a map,
    //      but then again this seems to only allow the peep to go on
    //      rides they haven't been on before.
    if (item_standard_flags & PEEP_ITEM_MAP)
    {
        // Consider rides that peep hasn't been on yet
        for (auto& ride : GetRideManager())
        {
            if (!HasRidden(&ride))
            {
                rideConsideration[ride.id] = true;
            }
        }
    }
    else
    {
        // Take nearby rides into consideration
        constexpr auto radius = 10 * 32;
        int32_t cx = floor2(x, 32);
        int32_t cy = floor2(y, 32);
        for (int32_t tileX = cx - radius; tileX <= cx + radius; tileX += 32)
        {
            for (int32_t tileY = cy - radius; tileY <= cy + radius; tileY += 32)
            {
                if (map_is_location_valid({ tileX, tileY }))
                {
                    auto tileElement = map_get_first_element_at(tileX >> 5, tileY >> 5);
                    if (tileElement != nullptr)
                    {
                        do
                        {
                            if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
                            {
                                auto rideIndex = tileElement->AsTrack()->GetRideIndex();
                                rideConsideration[rideIndex] = true;
                            }
                        } while (!(tileElement++)->IsLastForTile());
                    }
                }
            }
        }

        // Always take the tall rides into consideration (realistic as you can usually see them from anywhere in the park)
        for (auto& ride : GetRideManager())
        {
            if (ride.highest_drop_height > 66 || ride.excitement >= RIDE_RATING(8, 00))
            {
                rideConsideration[ride.id] = true;
            }
        }
    }

    return rideConsideration;
}

/**
 * This function is called whenever a peep is deciding whether or not they want
 * to go on a ride or visit a shop. They may be physically present at the
 * ride/shop, or they may just be thinking about it.
 *  rct2: 0x006960AB
 */
bool Guest::ShouldGoOnRide(Ride* ride, int32_t entranceNum, bool atQueue, bool thinking)
{
    // Indicates whether a peep is physically at the ride, or is just thinking about going on the ride.
    bool peepAtRide = !thinking;

    if (ride->status == RIDE_STATUS_OPEN && !(ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN))
    {
        // Peeps that are leaving the park will refuse to go on any rides, with the exception of free transport rides.
        assert(ride->type < std::size(RideData4));
        if (!(RideData4[ride->type].flags & RIDE_TYPE_FLAG4_TRANSPORT_RIDE) || ride->value == RIDE_VALUE_UNDEFINED
            || ride_get_price(ride) != 0)
        {
            if (peep_flags & PEEP_FLAGS_LEAVING_PARK)
            {
                ChoseNotToGoOnRide(ride, peepAtRide, false);
                return false;
            }
        }

        if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_IS_SHOP))
        {
            return ShouldGoToShop(ride, peepAtRide);
        }

        // This used to check !(flags & 2), but the function is only ever called with flags = 0, 1 or 6.
        // This means we can use the existing !(flags & 4) check.
        if (peepAtRide)
        {
            // Peeps won't join a queue that has 1000 peeps already in it.
            if (ride->stations[entranceNum].QueueLength >= 1000)
            {
                peep_tried_to_enter_full_queue(this, ride);
                return false;
            }

            // Rides without queues can only have one peep waiting at a time.
            if (!atQueue)
            {
                if (ride->stations[entranceNum].LastPeepInQueue != SPRITE_INDEX_NULL)
                {
                    peep_tried_to_enter_full_queue(this, ride);
                    return false;
                }
            }
            else
            {
                // Check if there's room in the queue for the peep to enter.
                if (ride->stations[entranceNum].LastPeepInQueue != SPRITE_INDEX_NULL)
                {
                    Peep* lastPeepInQueue = GET_PEEP(ride->stations[entranceNum].LastPeepInQueue);
                    if (abs(lastPeepInQueue->z - z) <= 6)
                    {
                        int32_t dx = abs(lastPeepInQueue->x - x);
                        int32_t dy = abs(lastPeepInQueue->y - y);
                        int32_t maxD = std::max(dx, dy);

                        // Unlike normal paths, peeps cannot overlap when queueing for a ride.
                        // This check enforces a minimum distance between peeps entering the queue.
                        if (maxD < 8)
                        {
                            peep_tried_to_enter_full_queue(this, ride);
                            return false;
                        }

                        // This checks if there's a peep standing still at the very end of the queue.
                        if (maxD <= 13 && lastPeepInQueue->time_in_queue > 10)
                        {
                            peep_tried_to_enter_full_queue(this, ride);
                            return false;
                        }
                    }
                }
            }
        }

        // Assuming the queue conditions are met, peeps will always go on free transport rides.
        // Ride ratings, recent crashes and weather will all be ignored.
        money16 ridePrice = ride_get_price(ride);
        if (!(RideData4[ride->type].flags & RIDE_TYPE_FLAG4_TRANSPORT_RIDE) || ride->value == RIDE_VALUE_UNDEFINED
            || ridePrice != 0)
        {
            if (previous_ride == ride->id)
            {
                ChoseNotToGoOnRide(ride, peepAtRide, false);
                return false;
            }

            // Basic price checks
            if (ridePrice != 0 && !peep_has_voucher_for_free_ride(this, ride) && !(gParkFlags & PARK_FLAGS_NO_MONEY))
            {
                if (ridePrice > cash_in_pocket)
                {
                    if (peepAtRide)
                    {
                        if (cash_in_pocket <= 0)
                        {
                            InsertNewThought(PEEP_THOUGHT_TYPE_SPENT_MONEY, PEEP_THOUGHT_ITEM_NONE);
                        }
                        else
                        {
                            InsertNewThought(PEEP_THOUGHT_TYPE_CANT_AFFORD_0, ride->id);
                        }
                    }
                    ChoseNotToGoOnRide(ride, peepAtRide, true);
                    return false;
                }
            }

            // If happy enough, peeps will ignore the fact that a ride has recently crashed.
            if (ride->last_crash_type != RIDE_CRASH_TYPE_NONE && happiness < 225)
            {
                if (peepAtRide)
                {
                    InsertNewThought(PEEP_THOUGHT_TYPE_NOT_SAFE, ride->id);
                    if (happiness_target >= 64)
                    {
                        happiness_target -= 8;
                    }
                    ride_update_popularity(ride, 0);
                }
                ChoseNotToGoOnRide(ride, peepAtRide, true);
                return false;
            }

            if (ride_has_ratings(ride))
            {
                // If a peep has already decided that they're going to go on a ride, they'll skip the weather and
                // excitement check and will only do a basic intensity check when they arrive at the ride itself.
                if (ride->id == guest_heading_to_ride_id)
                {
                    if (ride->intensity > RIDE_RATING(10, 00) && !gCheatsIgnoreRideIntensity)
                    {
                        peep_ride_is_too_intense(this, ride, peepAtRide);
                        return false;
                    }
                }

                // Peeps won't go on rides that aren't sufficiently undercover while it's raining.
                // The threshold is fairly low and only requires about 10-15% of the ride to be undercover.
                if (climate_is_raining() && (ride->sheltered_eighths) < 3)
                {
                    if (peepAtRide)
                    {
                        InsertNewThought(PEEP_THOUGHT_TYPE_NOT_WHILE_RAINING, ride->id);
                        if (happiness_target >= 64)
                        {
                            happiness_target -= 8;
                        }
                        ride_update_popularity(ride, 0);
                    }
                    ChoseNotToGoOnRide(ride, peepAtRide, true);
                    return false;
                }

                if (!gCheatsIgnoreRideIntensity)
                {
                    // Intensity calculations. Even though the max intensity can go up to 15, it's capped
                    // at 10.0 (before happiness calculations). A full happiness bar will increase the max
                    // intensity and decrease the min intensity by about 2.5.
                    ride_rating maxIntensity = std::min((intensity >> 4) * 100, 1000) + happiness;
                    ride_rating minIntensity = ((intensity & 0x0F) * 100) - happiness;
                    if (ride->intensity < minIntensity)
                    {
                        if (peepAtRide)
                        {
                            InsertNewThought(PEEP_THOUGHT_TYPE_MORE_THRILLING, ride->id);
                            if (happiness_target >= 64)
                            {
                                happiness_target -= 8;
                            }
                            ride_update_popularity(ride, 0);
                        }
                        ChoseNotToGoOnRide(ride, peepAtRide, true);
                        return false;
                    }
                    if (ride->intensity > maxIntensity)
                    {
                        peep_ride_is_too_intense(this, ride, peepAtRide);
                        return false;
                    }

                    // Nausea calculations.
                    ride_rating maxNausea = NauseaMaximumThresholds[(nausea_tolerance & 3)] + happiness;

                    if (ride->nausea > maxNausea)
                    {
                        if (peepAtRide)
                        {
                            InsertNewThought(PEEP_THOUGHT_TYPE_SICKENING, ride->id);
                            if (happiness_target >= 64)
                            {
                                happiness_target -= 8;
                            }
                            ride_update_popularity(ride, 0);
                        }
                        ChoseNotToGoOnRide(ride, peepAtRide, true);
                        return false;
                    }

                    // Very nauseous peeps will only go on very gentle rides.
                    if (ride->nausea >= FIXED_2DP(1, 40) && nausea > 160)
                    {
                        ChoseNotToGoOnRide(ride, peepAtRide, false);
                        return false;
                    }
                }
            }

            // If the ride has not yet been rated and is capable of having g-forces,
            // there's a 90% chance that the peep will ignore it.
            if (!ride_has_ratings(ride) && (RideData4[ride->type].flags & RIDE_TYPE_FLAG4_PEEP_CHECK_GFORCES))
            {
                if ((scenario_rand() & 0xFFFF) > 0x1999U)
                {
                    ChoseNotToGoOnRide(ride, peepAtRide, false);
                    return false;
                }

                if (!gCheatsIgnoreRideIntensity)
                {
                    if (ride->max_positive_vertical_g > FIXED_2DP(5, 00) || ride->max_negative_vertical_g < FIXED_2DP(-4, 00)
                        || ride->max_lateral_g > FIXED_2DP(4, 00))
                    {
                        ChoseNotToGoOnRide(ride, peepAtRide, false);
                        return false;
                    }
                }
            }

            uint32_t value = ride->value;

            // If the value of the ride hasn't yet been calculated, peeps will be willing to pay any amount for the ride.
            if (value != 0xFFFF && !peep_has_voucher_for_free_ride(this, ride) && !(gParkFlags & PARK_FLAGS_NO_MONEY))
            {
                // The amount peeps are willing to pay is decreased by 75% if they had to pay to enter the park.
                if (peep_flags & PEEP_FLAGS_HAS_PAID_FOR_PARK_ENTRY)
                    value /= 4;

                // Peeps won't pay more than twice the value of the ride.
                ridePrice = ride_get_price(ride);
                if (ridePrice > (money16)(value * 2))
                {
                    if (peepAtRide)
                    {
                        InsertNewThought(PEEP_THOUGHT_TYPE_BAD_VALUE, ride->id);
                        if (happiness_target >= 60)
                        {
                            happiness_target -= 16;
                        }
                        ride_update_popularity(ride, 0);
                    }
                    ChoseNotToGoOnRide(ride, peepAtRide, true);
                    return false;
                }

                // A ride is good value if the price is 50% or less of the ride value and the peep didn't pay to enter the park.
                if (ridePrice <= (money16)(value / 2) && peepAtRide)
                {
                    if (!(gParkFlags & PARK_FLAGS_NO_MONEY))
                    {
                        if (!(peep_flags & PEEP_FLAGS_HAS_PAID_FOR_PARK_ENTRY))
                        {
                            InsertNewThought(PEEP_THOUGHT_TYPE_GOOD_VALUE, ride->id);
                        }
                    }
                }
            }
        }

        // At this point, the peep has decided to go on the ride.
        if (peepAtRide)
        {
            ride_update_popularity(ride, 1);
        }

        if (ride->id == guest_heading_to_ride_id)
        {
            peep_reset_ride_heading(this);
        }

        ride->lifecycle_flags &= ~RIDE_LIFECYCLE_QUEUE_FULL;
        return true;
    }

    ChoseNotToGoOnRide(ride, peepAtRide, false);
    return false;
}

bool Guest::ShouldGoToShop(Ride* ride, bool peepAtShop)
{
    // Peeps won't go to the same shop twice in a row.
    if (ride->id == previous_ride)
    {
        ChoseNotToGoOnRide(ride, peepAtShop, true);
        return false;
    }

    if (ride->type == RIDE_TYPE_TOILETS)
    {
        if (toilet < 70)
        {
            ChoseNotToGoOnRide(ride, peepAtShop, true);
            return false;
        }

        // The amount that peeps are willing to pay to use the Toilets scales with their bathroom stat.
        // It effectively has a minimum of $0.10 (due to the check above) and a maximum of $0.60.
        if (ride->price * 40 > toilet)
        {
            if (peepAtShop)
            {
                InsertNewThought(PEEP_THOUGHT_TYPE_NOT_PAYING, ride->id);
                if (happiness_target >= 60)
                {
                    happiness_target -= 16;
                }
                ride_update_popularity(ride, 0);
            }
            ChoseNotToGoOnRide(ride, peepAtShop, true);
            return false;
        }
    }

    if (ride->type == RIDE_TYPE_FIRST_AID)
    {
        if (nausea < 128)
        {
            ChoseNotToGoOnRide(ride, peepAtShop, true);
            return false;
        }
    }

    // Basic price checks
    if (ride->price != 0 && ride->price > cash_in_pocket)
    {
        if (peepAtShop)
        {
            if (cash_in_pocket <= 0)
            {
                InsertNewThought(PEEP_THOUGHT_TYPE_SPENT_MONEY, PEEP_THOUGHT_ITEM_NONE);
            }
            else
            {
                InsertNewThought(PEEP_THOUGHT_TYPE_CANT_AFFORD_0, ride->id);
            }
        }
        ChoseNotToGoOnRide(ride, peepAtShop, true);
        return false;
    }

    if (peepAtShop)
    {
        ride_update_popularity(ride, 1);
        if (ride->id == guest_heading_to_ride_id)
        {
            peep_reset_ride_heading(this);
        }
    }
    return true;
}

// Used when no logging to an expend type required
void Guest::SpendMoney(money32 amount)
{
    money16 unused;
    SpendMoney(unused, amount);
}

/**
 *
 *  rct2: 0x0069926C
 * Expend type was previously an offset saved in 0x00F1AEC0
 */
void Guest::SpendMoney(money16& peep_expend_type, money32 amount)
{
    assert(!(gParkFlags & PARK_FLAGS_NO_MONEY));

    cash_in_pocket = std::max(0, cash_in_pocket - amount);
    cash_spent += amount;

    peep_expend_type += (money16)amount;

    window_invalidate_by_number(WC_PEEP, sprite_index);

    finance_payment(-amount, gCommandExpenditureType);

    if (gConfigGeneral.show_guest_purchases && !(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO))
    {
        // HACK Currently disabled for multiplayer due to limitation of all sprites
        //      needing to be synchronised
        if (network_get_mode() == NETWORK_MODE_NONE && !gOpenRCT2Headless)
        {
            rct_money_effect::CreateAt(amount, x, y, z, true);
        }
    }

    audio_play_sound_at_location(SoundId::Purchase, { x, y, z });
}

void Guest::SetHasRidden(const Ride* ride)
{
    rides_been_on[ride->id / 8] |= 1 << (ride->id % 8);
    SetHasRiddenRideType(ride->type);
}

bool Guest::HasRidden(const Ride* ride) const
{
    return rides_been_on[ride->id / 8] & (1 << (ride->id % 8));
}

void Guest::SetHasRiddenRideType(int32_t rideType)
{
    ride_types_been_on[rideType / 8] |= 1 << (rideType % 8);
}

bool Guest::HasRiddenRideType(int32_t rideType) const
{
    return ride_types_been_on[rideType / 8] & (1 << (rideType % 8));
}

void Guest::ChoseNotToGoOnRide(Ride* ride, bool peepAtRide, bool updateLastRide)
{
    if (peepAtRide && updateLastRide)
    {
        previous_ride = ride->id;
        previous_ride_time_out = 0;
    }

    if (ride->id == guest_heading_to_ride_id)
    {
        peep_reset_ride_heading(this);
    }
}

void Guest::ReadMap()
{
    if (action == PEEP_ACTION_NONE_1 || action == PEEP_ACTION_NONE_2)
    {
        action = PEEP_ACTION_READ_MAP;
        action_frame = 0;
        action_sprite_image_offset = 0;
        UpdateCurrentActionSpriteType();
    }
}

static bool peep_has_voucher_for_free_ride(Peep* peep, Ride* ride)
{
    return peep->item_standard_flags & PEEP_ITEM_VOUCHER && peep->voucher_type == VOUCHER_TYPE_RIDE_FREE
        && peep->voucher_arguments == ride->id;
}

/**
 * When the queue is full, peeps will ignore the ride when thinking about what to go on next.
 * Does not effect peeps that walk up to the queue entrance.
 * This flag is reset the next time a peep successfully joins the queue.
 */
static void peep_tried_to_enter_full_queue(Peep* peep, Ride* ride)
{
    ride->lifecycle_flags |= RIDE_LIFECYCLE_QUEUE_FULL;
    peep->previous_ride = ride->id;
    peep->previous_ride_time_out = 0;
    // Change status "Heading to" to "Walking" if queue is full
    if (ride->id == peep->guest_heading_to_ride_id)
    {
        peep_reset_ride_heading(peep);
    }
}

static void peep_reset_ride_heading(Peep* peep)
{
    peep->guest_heading_to_ride_id = RIDE_ID_NULL;
    peep->window_invalidate_flags |= PEEP_INVALIDATE_PEEP_ACTION;
}

static void peep_ride_is_too_intense(Guest* peep, Ride* ride, bool peepAtRide)
{
    if (peepAtRide)
    {
        peep->InsertNewThought(PEEP_THOUGHT_TYPE_INTENSE, ride->id);
        if (peep->happiness_target >= 64)
        {
            peep->happiness_target -= 8;
        }
        ride_update_popularity(ride, 0);
    }
    peep->ChoseNotToGoOnRide(ride, peepAtRide, true);
}

/**
 *
 *  rct2: 0x00691C6E
 */
static rct_vehicle* peep_choose_car_from_ride(Peep* peep, Ride* ride, std::vector<uint8_t>& car_array)
{
    uint8_t chosen_car = scenario_rand();
    if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_HAS_G_FORCES) && ((chosen_car & 0xC) != 0xC))
    {
        chosen_car = (scenario_rand() & 1) ? 0 : (uint8_t)car_array.size() - 1;
    }
    else
    {
        chosen_car = (chosen_car * (uint16_t)car_array.size()) >> 8;
    }

    peep->current_car = car_array[chosen_car];

    rct_vehicle* vehicle = GET_VEHICLE(ride->vehicles[peep->current_train]);

    for (int32_t i = peep->current_car; i > 0; --i)
    {
        vehicle = GET_VEHICLE(vehicle->next_vehicle_on_train);
    }

    return vehicle;
}

/**
 *
 *  rct2: 0x00691CD1
 */
static void peep_choose_seat_from_car(Peep* peep, Ride* ride, rct_vehicle* vehicle)
{
    uint8_t chosen_seat = vehicle->next_free_seat;

    if (ride->mode == RIDE_MODE_FORWARD_ROTATION || ride->mode == RIDE_MODE_BACKWARD_ROTATION)
    {
        chosen_seat = (((~vehicle->vehicle_sprite_type + 1) >> 3) & 0xF) * 2;
        if (vehicle->next_free_seat & 1)
        {
            chosen_seat++;
        }
    }
    peep->current_seat = chosen_seat;
    vehicle->next_free_seat++;

    vehicle->peep[peep->current_seat] = peep->sprite_index;
    vehicle->peep_tshirt_colours[peep->current_seat] = peep->tshirt_colour;
}

/**
 *
 *  rct2: 0x00691D27
 */
static void peep_go_to_ride_entrance(Guest* peep, Ride* ride)
{
    TileCoordsXYZD location = ride_get_entrance_location(ride, peep->current_ride_station);
    Guard::Assert(!location.isNull());
    int32_t x = location.x;
    int32_t y = location.y;

    uint8_t direction = location.direction;

    x *= 32;
    y *= 32;
    x += 16;
    y += 16;

    int16_t x_shift = word_981D6C[direction].x;
    int16_t y_shift = word_981D6C[direction].y;

    uint8_t shift_multiplier = 21;
    rct_ride_entry* rideEntry = get_ride_entry(ride->subtype);
    if (rideEntry != nullptr)
    {
        if (rideEntry->vehicles[rideEntry->default_vehicle].flags & VEHICLE_ENTRY_FLAG_MINI_GOLF
            || rideEntry->vehicles[rideEntry->default_vehicle].flags
                & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
        {
            shift_multiplier = 32;
        }
    }

    x_shift *= shift_multiplier;
    y_shift *= shift_multiplier;

    x += x_shift;
    y += y_shift;

    peep->destination_x = x;
    peep->destination_y = y;
    peep->destination_tolerance = 2;

    peep->SetState(PEEP_STATE_ENTERING_RIDE);
    peep->sub_state = PEEP_RIDE_IN_ENTRANCE;

    peep->rejoin_queue_timeout = 0;
    peep->time_on_ride = 0;

    peep->RemoveFromQueue();
}

static bool peep_find_vehicle_to_enter(Guest* peep, Ride* ride, std::vector<uint8_t>& car_array)
{
    uint8_t chosen_train = RideStation::NO_TRAIN;

    if (ride->mode == RIDE_MODE_BUMPERCAR || ride->mode == RIDE_MODE_RACE)
    {
        if (ride->lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING)
            return false;

        for (int32_t i = 0; i < ride->num_vehicles; ++i)
        {
            rct_vehicle* vehicle = GET_VEHICLE(ride->vehicles[i]);

            if (vehicle->next_free_seat >= vehicle->num_seats)
                continue;

            if (vehicle->status != VEHICLE_STATUS_WAITING_FOR_PASSENGERS)
                continue;
            chosen_train = i;
            break;
        }
    }
    else
    {
        chosen_train = ride->stations[peep->current_ride_station].TrainAtStation;
    }
    if (chosen_train == RideStation::NO_TRAIN || chosen_train >= MAX_VEHICLES_PER_RIDE)
    {
        return false;
    }

    peep->current_train = chosen_train;

    int32_t i = 0;

    uint16_t vehicle_id = ride->vehicles[chosen_train];
    rct_vehicle* vehicle = nullptr;

    for (; vehicle_id != SPRITE_INDEX_NULL; vehicle_id = vehicle->next_vehicle_on_train, i++)
    {
        vehicle = GET_VEHICLE(vehicle_id);

        uint8_t num_seats = vehicle->num_seats;
        if (vehicle_is_used_in_pairs(vehicle))
        {
            num_seats &= VEHICLE_SEAT_NUM_MASK;
            if (vehicle->next_free_seat & 1)
            {
                peep->current_car = i;
                peep_choose_seat_from_car(peep, ride, vehicle);
                peep_go_to_ride_entrance(peep, ride);
                return false;
            }
        }
        if (num_seats == vehicle->next_free_seat)
            continue;

        if (ride->mode == RIDE_MODE_FORWARD_ROTATION || ride->mode == RIDE_MODE_BACKWARD_ROTATION)
        {
            uint8_t position = (((~vehicle->vehicle_sprite_type + 1) >> 3) & 0xF) * 2;
            if (vehicle->peep[position] != SPRITE_INDEX_NULL)
                continue;
        }
        car_array.push_back(i);
    }

    return !car_array.empty();
}

static void peep_update_ride_at_entrance_try_leave(Guest* peep)
{
    // Destination Tolerance is zero when peep has completely
    // entered entrance
    if (peep->destination_tolerance == 0)
    {
        peep->RemoveFromQueue();
        peep->SetState(PEEP_STATE_FALLING);
    }
}

static bool peep_check_ride_price_at_entrance(Guest* peep, Ride* ride, money32 ridePrice)
{
    if ((peep->item_standard_flags & PEEP_ITEM_VOUCHER) && peep->voucher_type == VOUCHER_TYPE_RIDE_FREE
        && peep->voucher_arguments == peep->current_ride)
        return true;

    if (peep->cash_in_pocket <= 0 && !(gParkFlags & PARK_FLAGS_NO_MONEY))
    {
        peep->InsertNewThought(PEEP_THOUGHT_TYPE_SPENT_MONEY, PEEP_THOUGHT_ITEM_NONE);
        peep_update_ride_at_entrance_try_leave(peep);
        return false;
    }

    if (ridePrice > peep->cash_in_pocket)
    {
        peep->InsertNewThought(PEEP_THOUGHT_TYPE_CANT_AFFORD_0, peep->current_ride);
        peep_update_ride_at_entrance_try_leave(peep);
        return false;
    }

    uint16_t value = ride->value;
    if (value != RIDE_VALUE_UNDEFINED)
    {
        if (value * 2 < ridePrice)
        {
            peep->InsertNewThought(PEEP_THOUGHT_TYPE_BAD_VALUE, peep->current_ride);
            peep_update_ride_at_entrance_try_leave(peep);
            return false;
        }
    }
    return true;
}

/**
 * The satisfaction values calculated here are used to determine how happy the peep is with the ride,
 * and also affects the satisfaction stat of the ride itself. The factors that affect satisfaction include:
 * - The price of the ride compared to the ride's value
 * - How closely the intensity and nausea of the ride matches the peep's preferences
 * - How long the peep was waiting in the queue
 * - If the peep has been on the ride before, or on another ride of the same type
 */
static int16_t peep_calculate_ride_satisfaction(Guest* peep, Ride* ride)
{
    int16_t satisfaction = peep_calculate_ride_value_satisfaction(peep, ride);
    satisfaction += peep_calculate_ride_intensity_nausea_satisfaction(peep, ride);

    // Calculate satisfaction based on how long the peep has been in the queue for.
    // (For comparison: peeps start thinking "I've been queueing for a long time" at 3500 and
    // start leaving the queue at 4300.)
    if (peep->time_in_queue >= 4500)
        satisfaction -= 35;
    else if (peep->time_in_queue >= 2250)
        satisfaction -= 10;
    else if (peep->time_in_queue <= 750)
        satisfaction += 10;

    // Peeps get a small boost in satisfaction if they've been on a ride of the same type before,
    // and this boost is doubled if they've already been on this particular ride.
    if (peep->HasRiddenRideType(ride->type))
        satisfaction += 10;

    if (peep->HasRidden(get_ride(peep->current_ride)))
        satisfaction += 10;

    return satisfaction;
}

/**
 * Check to see if the specified ride should become the peep's favourite.
 * For this, a "ride rating" is calculated based on the excitement of the ride and the peep's current happiness.
 * As this value cannot exceed 255, the happier the peep is, the more irrelevant the ride's excitement becomes.
 * Due to the minimum happiness requirement, an excitement rating of more than 3.8 has no further effect.
 *
 * If the ride rating is higher than any ride the peep has already been on and the happiness criteria is met,
 * the ride becomes the peep's favourite. (This doesn't happen right away, but will be updated once the peep
 * exits the ride.)
 */
static void peep_update_favourite_ride(Peep* peep, Ride* ride)
{
    peep->peep_flags &= ~PEEP_FLAGS_RIDE_SHOULD_BE_MARKED_AS_FAVOURITE;
    uint8_t peepRideRating = std::clamp((ride->excitement / 4) + peep->happiness, 0, PEEP_MAX_HAPPINESS);
    if (peepRideRating >= peep->favourite_ride_rating)
    {
        if (peep->happiness >= 160 && peep->happiness_target >= 160)
        {
            peep->favourite_ride_rating = peepRideRating;
            peep->peep_flags |= PEEP_FLAGS_RIDE_SHOULD_BE_MARKED_AS_FAVOURITE;
        }
    }
}

/* rct2: 0x00695555 */
static int16_t peep_calculate_ride_value_satisfaction(Peep* peep, Ride* ride)
{
    if (gParkFlags & PARK_FLAGS_NO_MONEY)
    {
        return -30;
    }

    if (ride->value == RIDE_VALUE_UNDEFINED)
    {
        return -30;
    }

    money16 ridePrice = ride_get_price(ride);
    if (ride->value >= ridePrice)
    {
        return -5;
    }

    if ((ride->value + ((ride->value * peep->happiness) / 256)) >= ridePrice)
    {
        return -30;
    }

    return 0;
}

/**
 * Calculate satisfaction based on the intensity and nausea of the ride.
 * The best possible score from this section is achieved by having the intensity and nausea
 * of the ride fall exactly within the peep's preferences, but lower scores can still be achieved
 * if the peep's happiness is enough to offset it.
 */
static int16_t peep_calculate_ride_intensity_nausea_satisfaction(Peep* peep, Ride* ride)
{
    if (!ride_has_ratings(ride))
    {
        return 70;
    }

    uint8_t intensitySatisfaction = 3;
    uint8_t nauseaSatisfaction = 3;
    ride_rating maxIntensity = (peep->intensity >> 4) * 100;
    ride_rating minIntensity = (peep->intensity & 0xF) * 100;
    if (minIntensity <= ride->intensity && maxIntensity >= ride->intensity)
    {
        intensitySatisfaction--;
    }
    minIntensity -= peep->happiness * 2;
    maxIntensity += peep->happiness;
    if (minIntensity <= ride->intensity && maxIntensity >= ride->intensity)
    {
        intensitySatisfaction--;
    }
    minIntensity -= peep->happiness * 2;
    maxIntensity += peep->happiness;
    if (minIntensity <= ride->intensity && maxIntensity >= ride->intensity)
    {
        intensitySatisfaction--;
    }

    // Although it's not shown in the interface, a peep with Average or High nausea tolerance
    // has a minimum preferred nausea value. (For peeps with None or Low, this is set to zero.)
    ride_rating minNausea = NauseaMinimumThresholds[(peep->nausea_tolerance & 3)];
    ride_rating maxNausea = NauseaMaximumThresholds[(peep->nausea_tolerance & 3)];
    if (minNausea <= ride->nausea && maxNausea >= ride->nausea)
    {
        nauseaSatisfaction--;
    }
    minNausea -= peep->happiness * 2;
    maxNausea += peep->happiness;
    if (minNausea <= ride->nausea && maxNausea >= ride->nausea)
    {
        nauseaSatisfaction--;
    }
    minNausea -= peep->happiness * 2;
    maxNausea += peep->happiness;
    if (minNausea <= ride->nausea && maxNausea >= ride->nausea)
    {
        nauseaSatisfaction--;
    }

    uint8_t highestSatisfaction = std::max(intensitySatisfaction, nauseaSatisfaction);
    uint8_t lowestSatisfaction = std::min(intensitySatisfaction, nauseaSatisfaction);

    switch (highestSatisfaction)
    {
        default:
        case 0:
            return 70;
        case 1:
            switch (lowestSatisfaction)
            {
                default:
                case 0:
                    return 50;
                case 1:
                    return 35;
            }
        case 2:
            switch (lowestSatisfaction)
            {
                default:
                case 0:
                    return 35;
                case 1:
                    return 20;
                case 2:
                    return 10;
            }
        case 3:
            switch (lowestSatisfaction)
            {
                default:
                case 0:
                    return -35;
                case 1:
                    return -50;
                case 2:
                    return -60;
                case 3:
                    return -60;
            }
    }
}

/**
 * Update the nausea growth of the peep based on a ride. This is calculated based on:
 * - The nausea rating of the ride
 * - Their new happiness growth rate (the higher, the less nauseous)
 * - How hungry the peep is (+0% nausea at 50% hunger up to +100% nausea at 100% hunger)
 * - The peep's nausea tolerance (Final modifier: none: 100%, low: 50%, average: 25%, high: 12.5%)
 */
static void peep_update_ride_nausea_growth(Peep* peep, Ride* ride)
{
    uint32_t nauseaMultiplier = std::clamp(256 - peep->happiness_target, 64, 200);
    uint32_t nauseaGrowthRateChange = (ride->nausea * nauseaMultiplier) / 512;
    nauseaGrowthRateChange *= std::max(static_cast<uint8_t>(128), peep->hunger) / 64;
    nauseaGrowthRateChange >>= (peep->nausea_tolerance & 3);
    peep->nausea_target = (uint8_t)std::min(peep->nausea_target + nauseaGrowthRateChange, 255u);
}

static bool peep_should_go_on_ride_again(Peep* peep, Ride* ride)
{
    if (!ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_PEEP_WILL_RIDE_AGAIN))
        return false;
    if (!ride_has_ratings(ride))
        return false;
    if (ride->intensity > RIDE_RATING(10, 00) && !gCheatsIgnoreRideIntensity)
        return false;
    if (peep->happiness < 180)
        return false;
    if (peep->energy < 100)
        return false;
    if (peep->nausea > 160)
        return false;
    if (peep->hunger < 30)
        return false;
    if (peep->thirst < 20)
        return false;
    if (peep->toilet > 170)
        return false;

    uint8_t r = (scenario_rand() & 0xFF);
    if (r <= 128)
    {
        if (peep->no_of_rides > 7)
            return false;
        if (r > 64)
            return false;
    }

    return true;
}

static bool peep_should_preferred_intensity_increase(Peep* peep)
{
    if (gParkFlags & PARK_FLAGS_PREF_LESS_INTENSE_RIDES)
        return false;
    if (peep->happiness < 200)
        return false;

    return (scenario_rand() & 0xFF) >= peep->intensity;
}

static bool peep_really_liked_ride(Peep* peep, Ride* ride)
{
    if (peep->happiness < 215)
        return false;
    if (peep->nausea > 120)
        return false;
    if (!ride_has_ratings(ride))
        return false;
    if (ride->intensity > RIDE_RATING(10, 00) && !gCheatsIgnoreRideIntensity)
        return false;
    return true;
}

/**
 *
 *  rct2: 0x0069BC9A
 */
static PeepThoughtType peep_assess_surroundings(int16_t centre_x, int16_t centre_y, int16_t centre_z)
{
    if ((tile_element_height({ centre_x, centre_y })) > centre_z)
        return PEEP_THOUGHT_TYPE_NONE;

    uint16_t num_scenery = 0;
    uint16_t num_fountains = 0;
    uint16_t nearby_music = 0;
    uint16_t num_rubbish = 0;

    int16_t initial_x = std::max(centre_x - 160, 0);
    int16_t initial_y = std::max(centre_y - 160, 0);
    int16_t final_x = std::min(centre_x + 160, 8192);
    int16_t final_y = std::min(centre_y + 160, 8192);

    for (int16_t x = initial_x; x < final_x; x += 32)
    {
        for (int16_t y = initial_y; y < final_y; y += 32)
        {
            TileElement* tileElement = map_get_first_element_at(x / 32, y / 32);
            if (tileElement == nullptr)
                continue;
            do
            {
                Ride* ride;
                rct_scenery_entry* scenery;

                switch (tileElement->GetType())
                {
                    case TILE_ELEMENT_TYPE_PATH:
                        if (!tileElement->AsPath()->HasAddition())
                            break;

                        scenery = tileElement->AsPath()->GetAdditionEntry();
                        if (scenery == nullptr)
                        {
                            return PEEP_THOUGHT_TYPE_NONE;
                        }
                        if (tileElement->AsPath()->AdditionIsGhost())
                            break;

                        if (scenery->path_bit.flags
                            & (PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER | PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW))
                        {
                            num_fountains++;
                            break;
                        }
                        if (tileElement->AsPath()->IsBroken())
                        {
                            num_rubbish++;
                        }
                        break;
                    case TILE_ELEMENT_TYPE_LARGE_SCENERY:
                    case TILE_ELEMENT_TYPE_SMALL_SCENERY:
                        num_scenery++;
                        break;
                    case TILE_ELEMENT_TYPE_TRACK:
                        ride = get_ride(tileElement->AsTrack()->GetRideIndex());
                        if (ride != nullptr)
                        {
                            if (ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC && ride->status != RIDE_STATUS_CLOSED
                                && !(ride->lifecycle_flags & (RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED)))
                            {
                                if (ride->type == RIDE_TYPE_MERRY_GO_ROUND)
                                {
                                    nearby_music |= 1;
                                    break;
                                }

                                if (ride->music == MUSIC_STYLE_ORGAN)
                                {
                                    nearby_music |= 1;
                                    break;
                                }

                                if (ride->type == RIDE_TYPE_DODGEMS)
                                {
                                    // Dodgems drown out music?
                                    nearby_music |= 2;
                                }
                            }
                        }
                        break;
                }
            } while (!(tileElement++)->IsLastForTile());
        }
    }

    rct_litter* litter;
    for (uint16_t sprite_idx = gSpriteListHead[SPRITE_LIST_LITTER]; sprite_idx != SPRITE_INDEX_NULL; sprite_idx = litter->next)
    {
        litter = &(get_sprite(sprite_idx)->litter);

        int16_t dist_x = abs(litter->x - centre_x);
        int16_t dist_y = abs(litter->y - centre_y);
        if (std::max(dist_x, dist_y) <= 160)
        {
            num_rubbish++;
        }
    }

    if (num_fountains >= 5 && num_rubbish < 20)
        return PEEP_THOUGHT_TYPE_FOUNTAINS;

    if (num_scenery >= 40 && num_rubbish < 8)
        return PEEP_THOUGHT_TYPE_SCENERY;

    if (nearby_music == 1 && num_rubbish < 20)
        return PEEP_THOUGHT_TYPE_MUSIC;

    if (num_rubbish < 2 && !gCheatsDisableLittering)
        // if disable littering cheat is enabled, peeps will not have the "clean and tidy park" thought
        return PEEP_THOUGHT_TYPE_VERY_CLEAN;

    return PEEP_THOUGHT_TYPE_NONE;
}

/**
 *
 *  rct2: 0x0068F9A9
 */
static void peep_update_hunger(Peep* peep)
{
    if (peep->hunger >= 3)
    {
        peep->hunger -= 2;

        peep->energy_target = std::min(peep->energy_target + 2, PEEP_MAX_ENERGY_TARGET);
        peep->toilet = std::min(peep->toilet + 1, 255);
    }
}

/**
 * Main purpose is to decide when peeps leave the park due to
 * low happiness, low energy and (if appropriate) low money.
 *
 *  rct2: 0x0068F8CD
 */
static void peep_decide_whether_to_leave_park(Peep* peep)
{
    if (peep->energy_target >= 33)
    {
        peep->energy_target -= 2;
    }

    if (gClimateCurrent.Temperature >= 21 && peep->thirst >= 5)
    {
        peep->thirst--;
    }

    if (peep->outside_of_park != 0)
    {
        return;
    }

    /* Peeps that are happy enough, have enough energy and
     * (if appropriate) have enough money will always stay
     * in the park. */
    if (!(peep->peep_flags & PEEP_FLAGS_LEAVING_PARK))
    {
        if (gParkFlags & PARK_FLAGS_NO_MONEY)
        {
            if (peep->energy >= 70 && peep->happiness >= 60)
            {
                return;
            }
        }
        else
        {
            if (peep->energy >= 55 && peep->happiness >= 45 && peep->cash_in_pocket >= MONEY(5, 00))
            {
                return;
            }
        }
    }

    // Approx 95% chance of staying in the park
    if ((scenario_rand() & 0xFFFF) > 3276)
    {
        return;
    }

    // In the remaining 5% chance the peep leaves the park.
    peep_leave_park(peep);
}

/**
 *
 *  rct2: 0x0068F93E
 */
static void peep_leave_park(Peep* peep)
{
    peep->guest_heading_to_ride_id = RIDE_ID_NULL;
    if (peep->peep_flags & PEEP_FLAGS_LEAVING_PARK)
    {
        if (peep->peep_is_lost_countdown < 60)
        {
            return;
        }
    }
    else
    {
        peep->peep_is_lost_countdown = 254;
        peep->peep_flags |= PEEP_FLAGS_LEAVING_PARK;
        peep->peep_flags &= ~PEEP_FLAGS_PARK_ENTRANCE_CHOSEN;
    }

    peep->InsertNewThought(PEEP_THOUGHT_TYPE_GO_HOME, PEEP_THOUGHT_ITEM_NONE);

    rct_window* w = window_find_by_number(WC_PEEP, peep->sprite_index);
    if (w != nullptr)
        window_event_invalidate_call(w);
    window_invalidate_by_number(WC_PEEP, peep->sprite_index);
}

template<typename T> static void peep_head_for_nearest_ride(Guest* peep, bool considerOnlyCloseRides, T predicate)
{
    if (peep->state != PEEP_STATE_SITTING && peep->state != PEEP_STATE_WATCHING && peep->state != PEEP_STATE_WALKING)
    {
        return;
    }
    if (peep->peep_flags & PEEP_FLAGS_LEAVING_PARK)
        return;
    if (peep->x == LOCATION_NULL)
        return;
    if (peep->guest_heading_to_ride_id != RIDE_ID_NULL)
    {
        auto ride = get_ride(peep->guest_heading_to_ride_id);
        if (ride != nullptr && predicate(*ride))
        {
            return;
        }
    }

    std::bitset<MAX_RIDES> rideConsideration;
    if (!considerOnlyCloseRides && (peep->item_standard_flags & PEEP_ITEM_MAP))
    {
        // Consider all rides in the park
        for (const auto& ride : GetRideManager())
        {
            if (predicate(ride))
            {
                rideConsideration[ride.id] = true;
            }
        }
    }
    else
    {
        // Take nearby rides into consideration
        constexpr auto searchRadius = 10 * 32;
        int32_t cx = floor2(peep->x, 32);
        int32_t cy = floor2(peep->y, 32);
        for (auto x = cx - searchRadius; x <= cx + searchRadius; x += 32)
        {
            for (auto y = cy - searchRadius; y <= cy + searchRadius; y += 32)
            {
                if (map_is_location_valid({ x, y }))
                {
                    auto tileElement = map_get_first_element_at(x >> 5, y >> 5);
                    if (tileElement != nullptr)
                    {
                        do
                        {
                            if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
                            {
                                auto rideIndex = tileElement->AsTrack()->GetRideIndex();
                                auto ride = get_ride(rideIndex);
                                if (ride != nullptr && predicate(*ride))
                                {
                                    rideConsideration[rideIndex] = true;
                                }
                            }
                        } while (!(tileElement++)->IsLastForTile());
                    }
                }
            }
        }
    }

    // Filter the considered rides
    uint8_t potentialRides[MAX_RIDES];
    size_t numPotentialRides = 0;
    for (auto& ride : GetRideManager())
    {
        if (rideConsideration[ride.id])
        {
            if (!(ride.lifecycle_flags & RIDE_LIFECYCLE_QUEUE_FULL))
            {
                if (peep->ShouldGoOnRide(&ride, 0, false, true))
                {
                    potentialRides[numPotentialRides++] = ride.id;
                }
            }
        }
    }

    // Pick the closest ride
    Ride* closestRide{};
    auto closestRideDistance = std::numeric_limits<int32_t>::max();
    for (size_t i = 0; i < numPotentialRides; i++)
    {
        auto ride = get_ride(potentialRides[i]);
        if (ride != nullptr)
        {
            int32_t rideX = ride->stations[0].Start.x * 32;
            int32_t rideY = ride->stations[0].Start.y * 32;
            int32_t distance = abs(rideX - peep->x) + abs(rideY - peep->y);
            if (distance < closestRideDistance)
            {
                closestRide = ride;
                closestRideDistance = distance;
            }
        }
    }
    if (closestRide != nullptr)
    {
        // Head to that ride
        peep->guest_heading_to_ride_id = closestRide->id;
        peep->peep_is_lost_countdown = 200;
        peep_reset_pathfind_goal(peep);
        peep->window_invalidate_flags |= PEEP_INVALIDATE_PEEP_ACTION;
        peep->time_lost = 0;
    }
}

static void peep_head_for_nearest_ride_type(Guest* peep, int32_t rideType)
{
    auto considerOnlyCloseRides = rideType == RIDE_TYPE_FIRST_AID;
    return peep_head_for_nearest_ride(
        peep, considerOnlyCloseRides, [rideType](const Ride& ride) { return ride.type == rideType; });
}

static void peep_head_for_nearest_ride_with_flags(Guest* peep, int32_t rideTypeFlags)
{
    if ((rideTypeFlags & RIDE_TYPE_FLAG_IS_BATHROOM) && peep->HasFood())
    {
        return;
    }
    peep_head_for_nearest_ride(
        peep, false, [rideTypeFlags](const Ride& ride) { return ride_type_has_flag(ride.type, rideTypeFlags); });
}

/**
 *
 *  rct2: 0x00699FE3
 * Stops peeps that are having thoughts
 * such as "I'm hungry" after visiting a food shop.
 * Works for Thirst/Hungry/Low Money/Bathroom
 */
void Guest::StopPurchaseThought(uint8_t ride_type)
{
    uint8_t thoughtType = PEEP_THOUGHT_TYPE_HUNGRY;

    if (!ride_type_has_flag(ride_type, RIDE_TYPE_FLAG_SELLS_FOOD))
    {
        thoughtType = PEEP_THOUGHT_TYPE_THIRSTY;
        if (!ride_type_has_flag(ride_type, RIDE_TYPE_FLAG_SELLS_DRINKS))
        {
            thoughtType = PEEP_THOUGHT_TYPE_RUNNING_OUT;
            if (ride_type != RIDE_TYPE_CASH_MACHINE)
            {
                thoughtType = PEEP_THOUGHT_TYPE_BATHROOM;
                if (!ride_type_has_flag(ride_type, RIDE_TYPE_FLAG_IS_BATHROOM))
                {
                    return;
                }
            }
        }
    }

    // Remove the related thought
    for (int32_t i = 0; i < PEEP_MAX_THOUGHTS; ++i)
    {
        rct_peep_thought* thought = &thoughts[i];

        if (thought->type == PEEP_THOUGHT_TYPE_NONE)
            break;

        if (thought->type != thoughtType)
            continue;

        if (i < PEEP_MAX_THOUGHTS - 1)
        {
            memmove(thought, thought + 1, sizeof(rct_peep_thought) * (PEEP_MAX_THOUGHTS - i - 1));
        }

        thoughts[PEEP_MAX_THOUGHTS - 1].type = PEEP_THOUGHT_TYPE_NONE;

        window_invalidate_flags |= PEEP_INVALIDATE_PEEP_THOUGHTS;
        i--;
    }
}

/**
 *
 *  rct2: 0x0069AEB7
 */
static bool peep_should_use_cash_machine(Peep* peep, ride_id_t rideIndex)
{
    if (gParkFlags & PARK_FLAGS_NO_MONEY)
        return false;
    if (peep->peep_flags & PEEP_FLAGS_LEAVING_PARK)
        return false;
    if (peep->cash_in_pocket > MONEY(20, 00))
        return false;
    if (115 + (scenario_rand() % 128) > peep->happiness)
        return false;
    if (peep->energy < 80)
        return false;

    auto ride = get_ride(rideIndex);
    if (ride != nullptr)
    {
        ride_update_satisfaction(ride, peep->happiness >> 6);
        ride->cur_num_customers++;
        ride->total_customers++;
        ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;
    }
    return true;
}

/**
 *
 *  rct2: 0x006912A3
 */
void Guest::UpdateBuying()
{
    if (!CheckForPath())
        return;

    auto ride = get_ride(current_ride);
    if (ride == nullptr || ride->status != RIDE_STATUS_OPEN)
    {
        SetState(PEEP_STATE_FALLING);
        return;
    }

    if (sub_state == 1)
    {
        if (action != PEEP_ACTION_NONE_2)
        {
            UpdateAction();
            Invalidate();
            return;
        }

        if (ride->type == RIDE_TYPE_CASH_MACHINE)
        {
            if (current_ride != previous_ride)
            {
                cash_in_pocket += MONEY(50, 00);
            }
            window_invalidate_by_number(WC_PEEP, sprite_index);
        }
        sprite_direction ^= 0x10;
        destination_x = next_x + 16;
        destination_y = next_y + 16;
        direction = direction_reverse(direction);

        SetState(PEEP_STATE_WALKING);
        return;
    }

    bool item_bought = false;

    if (current_ride != previous_ride)
    {
        if (ride->type == RIDE_TYPE_CASH_MACHINE)
        {
            item_bought = peep_should_use_cash_machine(this, current_ride);
            if (!item_bought)
            {
                previous_ride = current_ride;
                previous_ride_time_out = 0;
            }
            else
            {
                action = PEEP_ACTION_WITHDRAW_MONEY;
                action_frame = 0;
                action_sprite_image_offset = 0;

                UpdateCurrentActionSpriteType();

                ride->no_primary_items_sold++;
            }
        }
        else
        {
            rct_ride_entry* ride_type = get_ride_entry(ride->subtype);
            if (ride_type == nullptr)
            {
                return;
            }
            if (ride_type->shop_item_secondary != SHOP_ITEM_NONE)
            {
                money16 price = ride->price_secondary;

                item_bought = DecideAndBuyItem(ride, ride_type->shop_item_secondary, price);
                if (item_bought)
                {
                    ride->no_secondary_items_sold++;
                }
            }

            if (!item_bought && ride_type->shop_item != SHOP_ITEM_NONE)
            {
                money16 price = ride->price;

                item_bought = DecideAndBuyItem(ride, ride_type->shop_item, price);
                if (item_bought)
                {
                    ride->no_primary_items_sold++;
                }
            }
        }
    }

    if (item_bought)
    {
        ride_update_popularity(ride, 1);

        StopPurchaseThought(ride->type);
    }
    else
    {
        ride_update_popularity(ride, 0);
    }
    sub_state = 1;
}

/**
 *
 *  rct2: 0x00691A3B
 */
void Guest::UpdateRideAtEntrance()
{
    auto ride = get_ride(current_ride);
    if (ride == nullptr)
        return;

    // The peep will keep advancing in the entranceway
    // whilst in this state. When it has reached the very
    // front of the queue destination tolerance is set to
    // zero to indicate it is final decision time (try_leave will pass).
    // When a peep has to return to the queue without getting on a ride
    // this is the state it will return to.
    if (destination_tolerance != 0)
    {
        int16_t xy_distance;
        if (auto loc = UpdateAction(xy_distance))
        {
            int16_t actionZ = z;
            if (xy_distance < 16)
            {
                auto entrance = ride_get_entrance_location(ride, current_ride_station);
                actionZ = entrance.z * 8 + 2;
            }
            MoveTo((*loc).x, (*loc).y, actionZ);
        }
        else
        {
            destination_tolerance = 0;
            sprite_direction ^= (1 << 4);
            Invalidate();
        }
    }

    std::vector<uint8_t> carArray;

    if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_NO_VEHICLES))
    {
        if (ride->num_riders >= ride->operation_option)
            return;
    }
    else
    {
        if (!peep_find_vehicle_to_enter(this, ride, carArray))
            return;
    }

    if (ride->status != RIDE_STATUS_OPEN || ride->vehicle_change_timeout != 0)
    {
        peep_update_ride_at_entrance_try_leave(this);
        return;
    }

    if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)
        return;

    money16 ridePrice = ride_get_price(ride);
    if (ridePrice != 0)
    {
        if (!peep_check_ride_price_at_entrance(this, ride, ridePrice))
            return;
    }

    if (!ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_NO_VEHICLES))
    {
        rct_vehicle* vehicle = peep_choose_car_from_ride(this, ride, carArray);
        peep_choose_seat_from_car(this, ride, vehicle);
    }
    peep_go_to_ride_entrance(this, ride);
}

/** rct2: 0x00981FD4, 0x00981FD6 */
static constexpr const LocationXY16 _MazeEntranceStart[] = {
    { 8, 8 },
    { 8, 24 },
    { 24, 24 },
    { 24, 8 },
};

static void peep_update_ride_leave_entrance_maze(Guest* peep, Ride* ride, TileCoordsXYZD& entrance_loc)
{
    peep->maze_last_edge = entrance_loc.direction + 1;
    entrance_loc.x *= 32;
    entrance_loc.y *= 32;

    entrance_loc.x += CoordsDirectionDelta[entrance_loc.direction].x;
    entrance_loc.y += CoordsDirectionDelta[entrance_loc.direction].y;

    uint8_t direction = entrance_loc.direction * 4 + 11;
    if (scenario_rand() & 0x40)
    {
        direction += 4;
        peep->maze_last_edge += 2;
    }

    direction &= 0xF;
    // Direction is 11, 15, 3, or 7
    peep->var_37 = direction;
    peep->maze_last_edge &= 3;

    entrance_loc.x += _MazeEntranceStart[direction / 4].x;
    entrance_loc.y += _MazeEntranceStart[direction / 4].y;

    peep->destination_x = entrance_loc.x;
    peep->destination_y = entrance_loc.y;
    peep->destination_tolerance = 3;

    ride->cur_num_customers++;
    peep->OnEnterRide(peep->current_ride);
    peep->sub_state = PEEP_RIDE_MAZE_PATHFINDING;
}

static void peep_update_ride_leave_entrance_spiral_slide(Guest* peep, Ride* ride, TileCoordsXYZD& entrance_loc)
{
    entrance_loc.x = ride->stations[peep->current_ride_station].Start.x * 32;
    entrance_loc.y = ride->stations[peep->current_ride_station].Start.y * 32;

    TileElement* tile_element = ride_get_station_start_track_element(ride, peep->current_ride_station);

    uint8_t direction_track = (tile_element == nullptr ? 0 : tile_element->GetDirection());

    peep->var_37 = (entrance_loc.direction << 2) | (direction_track << 4);

    const CoordsXY slidePlatformDestination = SpiralSlideWalkingPath[peep->var_37];

    entrance_loc.x += slidePlatformDestination.x;
    entrance_loc.y += slidePlatformDestination.y;

    peep->destination_x = entrance_loc.x;
    peep->destination_y = entrance_loc.y;
    peep->current_car = 0;

    ride->cur_num_customers++;
    peep->OnEnterRide(peep->current_ride);
    peep->sub_state = PEEP_RIDE_APPROACH_SPIRAL_SLIDE;
}

static uint8_t peep_get_waypointed_seat_location(
    Peep* peep, Ride* ride, rct_ride_entry_vehicle* vehicle_type, uint8_t track_direction)
{
    // The seatlocation can be split into segments around the ride base
    // to decide the segment first split off the segmentable seat location
    // from the fixed section
    uint8_t seatLocationSegment = peep->current_seat & 0x7;
    uint8_t seatLocationFixed = peep->current_seat & 0xF8;

    // Enterprise has more segments (8) compared to the normal (4)
    if (ride->type != RIDE_TYPE_ENTERPRISE)
        track_direction *= 2;

    // Type 1 loading doesn't do segments and all peeps go to the same
    // location on the ride
    if (vehicle_type->peep_loading_waypoint_segments == 0)
    {
        track_direction /= 2;
        seatLocationSegment = 0;
        seatLocationFixed = 0;
    }
    seatLocationSegment += track_direction;
    seatLocationSegment &= 0x7;
    return seatLocationSegment + seatLocationFixed;
}

static void peep_update_ride_leave_entrance_waypoints(Peep* peep, Ride* ride)
{
    TileCoordsXYZD entranceLocation = ride_get_entrance_location(ride, peep->current_ride_station);
    Guard::Assert(!entranceLocation.isNull());
    uint8_t direction_entrance = entranceLocation.direction;

    LocationXY16 waypoint;
    waypoint.x = ride->stations[peep->current_ride_station].Start.x * 32 + 16;
    waypoint.y = ride->stations[peep->current_ride_station].Start.y * 32 + 16;

    TileElement* tile_element = ride_get_station_start_track_element(ride, peep->current_ride_station);

    uint8_t direction_track = (tile_element == nullptr ? 0 : tile_element->GetDirection());

    auto vehicle = GET_VEHICLE(ride->vehicles[peep->current_train]);
    auto ride_entry = get_ride_entry(vehicle->ride_subtype);
    auto vehicle_type = &ride_entry->vehicles[vehicle->vehicle_type];

    peep->var_37 = (direction_entrance | peep_get_waypointed_seat_location(peep, ride, vehicle_type, direction_track) * 4) * 4;

    if (ride->type == RIDE_TYPE_ENTERPRISE)
    {
        waypoint.x = vehicle->x;
        waypoint.y = vehicle->y;
    }

    Guard::Assert(vehicle_type->peep_loading_waypoints.size() >= (size_t)(peep->var_37 / 4));
    waypoint.x += vehicle_type->peep_loading_waypoints[peep->var_37 / 4][0].x;
    waypoint.y += vehicle_type->peep_loading_waypoints[peep->var_37 / 4][0].y;

    peep->destination_x = waypoint.x;
    peep->destination_y = waypoint.y;
    peep->sub_state = PEEP_RIDE_APPROACH_VEHICLE_WAYPOINTS;
}

/**
 *
 *  rct2: 0x006921D3
 */
void Guest::UpdateRideAdvanceThroughEntrance()
{
    auto ride = get_ride(current_ride);
    if (ride == nullptr)
        return;

    int16_t actionZ, xy_distance;

    auto ride_entry = ride->GetRideEntry();

    if (auto loc = UpdateAction(xy_distance))
    {
        uint16_t distanceThreshold = 16;
        if (ride_entry != nullptr)
        {
            uint8_t vehicle = ride_entry->default_vehicle;
            if (ride_entry->vehicles[vehicle].flags & VEHICLE_ENTRY_FLAG_MINI_GOLF
                || ride_entry->vehicles[vehicle].flags & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
            {
                distanceThreshold = 28;
            }
        }

        if (sub_state == PEEP_RIDE_IN_ENTRANCE && xy_distance < distanceThreshold)
        {
            sub_state = PEEP_RIDE_FREE_VEHICLE_CHECK;
        }

        actionZ = ride->stations[current_ride_station].Height * 8;

        distanceThreshold += 4;
        if (xy_distance < distanceThreshold)
        {
            actionZ += RideData5[ride->type].z;
        }

        MoveTo((*loc).x, (*loc).y, actionZ);
        return;
    }

    Guard::Assert(sub_state == PEEP_RIDE_LEAVE_ENTRANCE, "Peep substate should be LEAVE_ENTRANCE");
    if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_NO_VEHICLES))
    {
        TileCoordsXYZD entranceLocation = ride_get_entrance_location(ride, current_ride_station);
        Guard::Assert(!entranceLocation.isNull());

        if (ride->type == RIDE_TYPE_MAZE)
        {
            peep_update_ride_leave_entrance_maze(this, ride, entranceLocation);
            return;
        }
        else if (ride->type == RIDE_TYPE_SPIRAL_SLIDE)
        {
            peep_update_ride_leave_entrance_spiral_slide(this, ride, entranceLocation);
            return;
        }
        else
        {
            // If the ride type was changed guests will become stuck.
            // Inform the player about this if its a new issue or hasn't been addressed within 120 seconds.
            if ((ride->current_issues & RIDE_ISSUE_GUESTS_STUCK) == 0 || gCurrentTicks - ride->last_issue_time > 3000)
            {
                ride->current_issues |= RIDE_ISSUE_GUESTS_STUCK;
                ride->last_issue_time = gCurrentTicks;

                ride->FormatNameTo(gCommonFormatArgs);
                if (gConfigNotifications.ride_warnings)
                {
                    news_item_add_to_queue(NEWS_ITEM_RIDE, STR_GUESTS_GETTING_STUCK_ON_RIDE, current_ride);
                }
            }

            return;
        }
    }

    rct_vehicle* vehicle = GET_VEHICLE(ride->vehicles[current_train]);
    for (int32_t i = current_car; i != 0; --i)
    {
        vehicle = GET_VEHICLE(vehicle->next_vehicle_on_train);
    }

    ride_entry = get_ride_entry(vehicle->ride_subtype);
    if (ride_entry == nullptr)
    {
        return;
    }

    rct_ride_entry_vehicle* vehicle_type = &ride_entry->vehicles[vehicle->vehicle_type];

    if (vehicle_type->flags & VEHICLE_ENTRY_FLAG_LOADING_WAYPOINTS)
    {
        peep_update_ride_leave_entrance_waypoints(this, ride);
        return;
    }

    if (vehicle_type->flags & VEHICLE_ENTRY_FLAG_DODGEM_CAR_PLACEMENT)
    {
        destination_x = vehicle->x;
        destination_y = vehicle->y;
        destination_tolerance = 15;
        sub_state = PEEP_RIDE_APPROACH_VEHICLE;
        return;
    }

    int8_t load_position = 0;
    // Safe, in case current seat > number of loading positions
    uint16_t numSeatPositions = static_cast<uint16_t>(vehicle_type->peep_loading_positions.size());
    if (numSeatPositions != 0)
    {
        size_t loadPositionIndex = numSeatPositions - 1;
        if (current_seat < numSeatPositions)
        {
            loadPositionIndex = current_seat;
        }
        load_position = vehicle_type->peep_loading_positions[loadPositionIndex];
    }

    switch (vehicle->sprite_direction / 8)
    {
        case 0:
            destination_x = vehicle->x - load_position;
            break;
        case 1:
            destination_y = vehicle->y + load_position;
            break;
        case 2:
            destination_x = vehicle->x + load_position;
            break;
        case 3:
            destination_y = vehicle->y - load_position;
            break;
    }

    sub_state = PEEP_RIDE_APPROACH_VEHICLE;
}

/**
 *
 *  rct2: 0x0069321D
 */
static void peep_go_to_ride_exit(Peep* peep, Ride* ride, int16_t x, int16_t y, int16_t z, uint8_t exit_direction)
{
    z += RideData5[ride->type].z;

    peep->MoveTo(x, y, z);

    Guard::Assert(peep->current_ride_station < MAX_STATIONS);
    auto exit = ride_get_exit_location(ride, peep->current_ride_station);
    Guard::Assert(!exit.isNull());
    x = exit.x;
    y = exit.y;
    x *= 32;
    y *= 32;
    x += 16;
    y += 16;

    int16_t x_shift = word_981D6C[exit_direction].x;
    int16_t y_shift = word_981D6C[exit_direction].y;

    int16_t shift_multiplier = 20;

    rct_ride_entry* rideEntry = get_ride_entry(ride->subtype);
    if (rideEntry != nullptr)
    {
        rct_ride_entry_vehicle* vehicle_entry = &rideEntry->vehicles[rideEntry->default_vehicle];
        if (vehicle_entry->flags & VEHICLE_ENTRY_FLAG_MINI_GOLF
            || vehicle_entry->flags & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
        {
            shift_multiplier = 32;
        }
    }

    x_shift *= shift_multiplier;
    y_shift *= shift_multiplier;

    x -= x_shift;
    y -= y_shift;

    peep->destination_x = x;
    peep->destination_y = y;
    peep->destination_tolerance = 2;

    peep->sprite_direction = exit_direction * 8;
    peep->sub_state = PEEP_RIDE_APPROACH_EXIT;
}

/**
 *
 *  rct2: 0x006920B4
 */
void Guest::UpdateRideFreeVehicleEnterRide(Ride* ride)
{
    money16 ridePrice = ride_get_price(ride);
    if (ridePrice != 0)
    {
        if ((item_standard_flags & PEEP_ITEM_VOUCHER) && (voucher_type == VOUCHER_TYPE_RIDE_FREE)
            && (voucher_arguments == current_ride))
        {
            item_standard_flags &= ~PEEP_ITEM_VOUCHER;
            window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY;
        }
        else
        {
            ride->total_profit += ridePrice;
            ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_INCOME;
            gCommandExpenditureType = RCT_EXPENDITURE_TYPE_PARK_RIDE_TICKETS;
            SpendMoney(paid_on_rides, ridePrice);
        }
    }

    sub_state = PEEP_RIDE_LEAVE_ENTRANCE;
    uint8_t queueTime = days_in_queue;
    if (queueTime < 253)
        queueTime += 3;

    queueTime /= 2;
    if (queueTime != ride->stations[current_ride_station].QueueTime)
    {
        ride->stations[current_ride_station].QueueTime = queueTime;
        window_invalidate_by_number(WC_RIDE, current_ride);
    }

    if (peep_flags & PEEP_FLAGS_TRACKING)
    {
        auto nameArgLen = FormatNameTo(gCommonFormatArgs);
        ride->FormatNameTo(gCommonFormatArgs + nameArgLen);

        rct_string_id msg_string;
        if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_IN_RIDE))
            msg_string = STR_PEEP_TRACKING_PEEP_IS_IN_X;
        else
            msg_string = STR_PEEP_TRACKING_PEEP_IS_ON_X;

        if (gConfigNotifications.guest_on_ride)
        {
            news_item_add_to_queue(NEWS_ITEM_PEEP_ON_RIDE, msg_string, sprite_index);
        }
    }

    if (ride->type == RIDE_TYPE_SPIRAL_SLIDE)
    {
        SwitchToSpecialSprite(1);
    }

    UpdateRideAdvanceThroughEntrance();
}

/**
 *
 *  rct2: 0x00691FD4
 */
static void peep_update_ride_no_free_vehicle_rejoin_queue(Peep* peep, Ride* ride)
{
    TileCoordsXYZD entranceLocation = ride_get_entrance_location(ride, peep->current_ride_station);

    int32_t x = entranceLocation.x * 32;
    int32_t y = entranceLocation.y * 32;
    x += 16 - word_981D6C[entranceLocation.direction].x * 20;
    y += 16 - word_981D6C[entranceLocation.direction].y * 20;

    peep->destination_x = x;
    peep->destination_y = y;
    peep->destination_tolerance = 2;

    peep->SetState(PEEP_STATE_QUEUING_FRONT);
    peep->sub_state = PEEP_RIDE_AT_ENTRANCE;

    ride->QueueInsertGuestAtFront(peep->current_ride_station, peep);
}

/**
 *
 *  rct2: 0x00691E42
 * Note: Before this was the entry
 * point for sub state 1 and 3. The
 * check has been removed that would
 * branch it out to 1 and 3. Now uses
 * separate functions.
 */
void Guest::UpdateRideFreeVehicleCheck()
{
    auto ride = get_ride(current_ride);
    if (ride == nullptr)
        return;

    if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_NO_VEHICLES))
    {
        if (ride->status != RIDE_STATUS_OPEN || ride->vehicle_change_timeout != 0 || (++rejoin_queue_timeout) == 0)
        {
            peep_update_ride_no_free_vehicle_rejoin_queue(this, ride);
            return;
        }

        UpdateRideFreeVehicleEnterRide(ride);
        return;
    }

    rct_vehicle* vehicle = GET_VEHICLE(ride->vehicles[current_train]);
    for (int32_t i = current_car; i != 0; --i)
    {
        vehicle = GET_VEHICLE(vehicle->next_vehicle_on_train);
    }

    rct_ride_entry* ride_entry = get_ride_entry(vehicle->ride_subtype);
    if (ride_entry == nullptr)
    {
        return;
    }

    if (ride_entry->vehicles[0].flags & VEHICLE_ENTRY_FLAG_MINI_GOLF)
    {
        vehicle->mini_golf_flags &= ~(1 << 5);

        for (size_t i = 0; i < ride->num_vehicles; ++i)
        {
            if (ride->vehicles[i] == SPRITE_INDEX_NULL)
                continue;

            rct_vehicle* train = GET_VEHICLE(ride->vehicles[i]);
            rct_vehicle* second_vehicle = GET_VEHICLE(train->next_vehicle_on_train);

            if (second_vehicle->num_peeps == 0)
                continue;

            if (second_vehicle->mini_golf_flags & (1 << 5))
                continue;

            return;
        }
    }

    if (!vehicle_is_used_in_pairs(vehicle))
    {
        UpdateRideFreeVehicleEnterRide(ride);
        return;
    }

    if (ride->mode == RIDE_MODE_FORWARD_ROTATION || ride->mode == RIDE_MODE_BACKWARD_ROTATION)
    {
        if (current_seat & 1 || !(vehicle->next_free_seat & 1))
        {
            UpdateRideFreeVehicleEnterRide(ride);
            return;
        }
    }
    else
    {
        uint8_t seat = current_seat | 1;
        if (seat < vehicle->next_free_seat)
        {
            UpdateRideFreeVehicleEnterRide(ride);
            return;
        }
    }

    rct_vehicle* currentTrain = GET_VEHICLE(ride->vehicles[current_train]);
    if (ride->status == RIDE_STATUS_OPEN && ++rejoin_queue_timeout != 0
        && !(currentTrain->update_flags & VEHICLE_UPDATE_FLAG_TRAIN_READY_DEPART))
    {
        return;
    }

    if (ride->mode != RIDE_MODE_FORWARD_ROTATION && ride->mode != RIDE_MODE_BACKWARD_ROTATION)
    {
        if (vehicle->next_free_seat - 1 != current_seat)
            return;
    }

    vehicle->next_free_seat--;
    vehicle->peep[current_seat] = SPRITE_INDEX_NULL;

    peep_update_ride_no_free_vehicle_rejoin_queue(this, ride);
}

void Guest::UpdateRideApproachVehicle()
{
    if (auto loc = UpdateAction())
    {
        MoveTo((*loc).x, (*loc).y, z);
        return;
    }
    sub_state = PEEP_RIDE_ENTER_VEHICLE;
}

void Guest::UpdateRideEnterVehicle()
{
    auto* ride = get_ride(current_ride);
    if (ride != nullptr)
    {
        auto* vehicle = GET_VEHICLE(ride->vehicles[current_train]);
        if (vehicle != nullptr)
        {
            for (int32_t i = current_car; i != 0; --i)
            {
                vehicle = GET_VEHICLE(vehicle->next_vehicle_on_train);
            }

            if (ride->mode != RIDE_MODE_FORWARD_ROTATION && ride->mode != RIDE_MODE_BACKWARD_ROTATION)
            {
                if (current_seat != vehicle->num_peeps)
                    return;
            }

            if (vehicle_is_used_in_pairs(vehicle))
            {
                auto* seatedPeep = GET_PEEP(vehicle->peep[current_seat ^ 1]);
                if (seatedPeep != nullptr)
                {
                    auto* seatedPeepAsGuest = seatedPeep->AsGuest();
                    if (seatedPeepAsGuest == nullptr || seatedPeepAsGuest->sub_state != PEEP_RIDE_ENTER_VEHICLE)
                        return;

                    vehicle->num_peeps++;
                    ride->cur_num_customers++;

                    vehicle->mass += seatedPeepAsGuest->mass;
                    seatedPeepAsGuest->MoveTo(LOCATION_NULL, 0, 0);
                    seatedPeepAsGuest->SetState(PEEP_STATE_ON_RIDE);
                    seatedPeepAsGuest->time_on_ride = 0;
                    seatedPeepAsGuest->sub_state = PEEP_RIDE_ON_RIDE;
                    seatedPeepAsGuest->OnEnterRide(current_ride);
                }
            }

            vehicle->num_peeps++;
            ride->cur_num_customers++;

            vehicle->mass += mass;
            invalidate_sprite_2(reinterpret_cast<rct_sprite*>(vehicle));

            MoveTo(LOCATION_NULL, 0, 0);

            SetState(PEEP_STATE_ON_RIDE);

            time_on_ride = 0;
            sub_state = PEEP_RIDE_ON_RIDE;
            OnEnterRide(current_ride);
        }
    }
}

/**
 *
 *  rct2: 0x00693028
 */
void Guest::UpdateRideLeaveVehicle()
{
    auto ride = get_ride(current_ride);
    if (ride == nullptr)
        return;

    rct_vehicle* vehicle = GET_VEHICLE(ride->vehicles[current_train]);
    uint8_t ride_station = vehicle->current_station;

    for (int32_t i = current_car; i != 0; --i)
    {
        vehicle = GET_VEHICLE(vehicle->next_vehicle_on_train);
    }

    // Check if ride is NOT Ferris Wheel.
    if (ride->mode != RIDE_MODE_FORWARD_ROTATION && ride->mode != RIDE_MODE_BACKWARD_ROTATION)
    {
        if (vehicle->num_peeps - 1 != current_seat)
            return;
    }

    action_sprite_image_offset++;
    if (action_sprite_image_offset & 3)
        return;

    action_sprite_image_offset = 0;

    vehicle->num_peeps--;
    vehicle->mass -= mass;
    invalidate_sprite_2((rct_sprite*)vehicle);

    if (ride_station >= MAX_STATIONS)
    {
        // HACK #5658: Some parks have hacked rides which end up in this state
        int8_t bestStationIndex = ride_get_first_valid_station_exit(ride);
        if (bestStationIndex == -1)
        {
            bestStationIndex = 0;
        }
        ride_station = bestStationIndex;
    }
    current_ride_station = ride_station;
    rct_ride_entry* rideEntry = get_ride_entry(vehicle->ride_subtype);
    if (rideEntry == nullptr)
    {
        return;
    }

    rct_ride_entry_vehicle* vehicle_entry = &rideEntry->vehicles[vehicle->vehicle_type];

    if (!(vehicle_entry->flags & VEHICLE_ENTRY_FLAG_LOADING_WAYPOINTS))
    {
        assert(current_ride_station < MAX_STATIONS);
        TileCoordsXYZD exitLocation = ride_get_exit_location(ride, current_ride_station);
        CoordsXYZD platformLocation;
        platformLocation.z = ride->stations[current_ride_station].Height;

        platformLocation.direction = direction_reverse(exitLocation.direction);

        if (!ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_16))
        {
            for (; !vehicle->IsHead(); vehicle = GET_VEHICLE(vehicle->prev_vehicle_on_ride))
            {
                uint16_t trackType = vehicle->track_type >> 2;
                if (trackType == TRACK_ELEM_FLAT || trackType > TRACK_ELEM_MIDDLE_STATION)
                    continue;

                TileElement* inner_map = map_get_first_element_at(vehicle->track_x / 32, vehicle->track_y / 32);
                if (inner_map == nullptr)
                    continue;
                for (;; inner_map++)
                {
                    if (inner_map->GetType() != TILE_ELEMENT_TYPE_TRACK)
                        continue;
                    if (inner_map->base_height == vehicle->track_z / 8)
                        break;
                }

                uint8_t stationIndex = inner_map->AsTrack()->GetStationIndex();
                if (stationIndex == current_ride_station)
                    break;
            }

            uint8_t shiftMultiplier = 12;
            uint8_t specialDirection = platformLocation.direction;

            rideEntry = get_ride_entry(ride->subtype);

            if (rideEntry != nullptr)
            {
                vehicle_entry = &rideEntry->vehicles[rideEntry->default_vehicle];

                if (vehicle_entry->flags & VEHICLE_ENTRY_FLAG_GO_KART)
                {
                    shiftMultiplier = 9;
                }

                if (vehicle_entry->flags & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
                {
                    specialDirection = ((vehicle->sprite_direction + 3) / 8) + 1;
                    specialDirection &= 3;

                    if (vehicle->var_CD == 6)
                        specialDirection = direction_reverse(specialDirection);
                }
            }

            int16_t xShift = word_981D6C[specialDirection].x;
            int16_t yShift = word_981D6C[specialDirection].y;

            platformLocation.x = vehicle->x + xShift * shiftMultiplier;
            platformLocation.y = vehicle->y + yShift * shiftMultiplier;
            platformLocation.z *= 8;

            peep_go_to_ride_exit(
                this, ride, platformLocation.x, platformLocation.y, platformLocation.z, platformLocation.direction);
            return;
        }

        platformLocation.x = vehicle->x + word_981D6C[platformLocation.direction].x * 12;
        platformLocation.y = vehicle->y + word_981D6C[platformLocation.direction].y * 12;

        // This can evaluate to false with buggy custom rides.
        if (current_seat < vehicle_entry->peep_loading_positions.size())
        {
            int8_t loadPosition = vehicle_entry->peep_loading_positions[current_seat];

            switch (vehicle->sprite_direction / 8)
            {
                case 0:
                    platformLocation.x -= loadPosition;
                    break;
                case 1:
                    platformLocation.y += loadPosition;
                    break;
                case 2:
                    platformLocation.x += loadPosition;
                    break;
                case 3:
                    platformLocation.y -= loadPosition;
                    break;
            }
        }
        else
        {
            log_verbose(
                "current_seat %d is too large! (Vehicle entry has room for %d.)", current_seat,
                vehicle_entry->peep_loading_positions.size());
        }

        platformLocation.z = ride->stations[current_ride_station].Height * 8;

        peep_go_to_ride_exit(
            this, ride, platformLocation.x, platformLocation.y, platformLocation.z, platformLocation.direction);
        return;
    }

    TileCoordsXYZD exitLocation = ride_get_exit_location(ride, current_ride_station);
    Guard::Assert(!exitLocation.isNull());
    CoordsXYZ waypointLoc;

    waypointLoc.z = (int16_t)exitLocation.z * 8 + RideData5[ride->type].z;
    waypointLoc.x = ride->stations[current_ride_station].Start.x * 32 + 16;
    waypointLoc.y = ride->stations[current_ride_station].Start.y * 32 + 16;

    TileElement* trackElement = ride_get_station_start_track_element(ride, current_ride_station);

    uint8_t station_direction = (trackElement == nullptr ? 0 : trackElement->GetDirection());

    vehicle = GET_VEHICLE(ride->vehicles[current_train]);

    rideEntry = get_ride_entry(vehicle->ride_subtype);
    rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle->vehicle_type];

    var_37 = ((exitLocation.direction | peep_get_waypointed_seat_location(this, ride, vehicleEntry, station_direction) * 4) * 4)
        | 1;

    if (ride->type == RIDE_TYPE_ENTERPRISE)
    {
        waypointLoc.x = vehicle->x;
        waypointLoc.y = vehicle->y;
    }

    Guard::Assert(vehicleEntry->peep_loading_waypoints.size() >= (size_t)(var_37 / 4));
    CoordsXYZ exitWaypointLoc = waypointLoc;

    exitWaypointLoc.x += vehicleEntry->peep_loading_waypoints[var_37 / 4][2].x;
    exitWaypointLoc.y += vehicleEntry->peep_loading_waypoints[var_37 / 4][2].y;

    if (ride->type == RIDE_TYPE_MOTION_SIMULATOR)
        exitWaypointLoc.z += 15;

    MoveTo(exitWaypointLoc.x, exitWaypointLoc.y, exitWaypointLoc.z);

    waypointLoc.x += vehicleEntry->peep_loading_waypoints[var_37 / 4][1].x;
    waypointLoc.y += vehicleEntry->peep_loading_waypoints[var_37 / 4][1].y;

    destination_x = waypointLoc.x;
    destination_y = waypointLoc.y;
    destination_tolerance = 2;
    sub_state = PEEP_RIDE_APPROACH_EXIT_WAYPOINTS;
}

/**
 *
 *  rct2: 0x0069376A
 */
static void peep_update_ride_prepare_for_exit(Peep* peep)
{
    auto ride = get_ride(peep->current_ride);
    if (ride == nullptr || peep->current_ride_station >= std::size(ride->stations))
        return;

    auto exit = ride_get_exit_location(ride, peep->current_ride_station);
    int16_t x = exit.x;
    int16_t y = exit.y;
    uint8_t exit_direction = exit.direction;

    x *= 32;
    y *= 32;
    x += 16;
    y += 16;

    int16_t x_shift = word_981D6C[exit_direction].x;
    int16_t y_shift = word_981D6C[exit_direction].y;

    int16_t shift_multiplier = 20;

    rct_ride_entry* ride_type = get_ride_entry(ride->subtype);
    if (ride_type != nullptr)
    {
        rct_ride_entry_vehicle* vehicle_entry = &ride_type->vehicles[ride_type->default_vehicle];
        if (vehicle_entry->flags & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
        {
            shift_multiplier = 32;
        }
    }

    x_shift *= shift_multiplier;
    y_shift *= shift_multiplier;

    x -= x_shift;
    y -= y_shift;

    peep->destination_x = x;
    peep->destination_y = y;
    peep->destination_tolerance = 2;
    peep->sub_state = PEEP_RIDE_IN_EXIT;
}

/**
 *
 *  rct2: 0x0069374F
 */
void Guest::UpdateRideApproachExit()
{
    if (auto loc = UpdateAction())
    {
        MoveTo((*loc).x, (*loc).y, z);
        return;
    }

    peep_update_ride_prepare_for_exit(this);
}

/**
 *
 *  rct2: 0x0069382E
 */
void Guest::UpdateRideInExit()
{
    auto ride = get_ride(current_ride);
    if (ride == nullptr)
        return;

    int16_t xy_distance;

    if (auto loc = UpdateAction(xy_distance))
    {
        if (xy_distance >= 16)
        {
            int16_t actionZ = ride->stations[current_ride_station].Height * 8;

            actionZ += RideData5[ride->type].z;
            MoveTo((*loc).x, (*loc).y, actionZ);
            return;
        }

        SwitchToSpecialSprite(0);
        MoveTo((*loc).x, (*loc).y, z);
    }

    if (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO)
    {
        uint8_t secondaryItem = RidePhotoItems[ride->type];
        if (DecideAndBuyItem(ride, secondaryItem, ride->price_secondary))
        {
            ride->no_secondary_items_sold++;
        }
    }
    sub_state = PEEP_RIDE_LEAVE_EXIT;
}
#pragma warning(default : 6011)
/**
 *
 *  rct2: 0x006926AD
 */
void Guest::UpdateRideApproachVehicleWaypoints()
{
    auto ride = get_ride(current_ride);
    if (ride == nullptr)
        return;

    int16_t xy_distance;
    uint8_t waypoint = var_37 & 3;

    if (auto loc = UpdateAction(xy_distance))
    {
        int16_t actionZ;
        // Motion simulators have steps this moves the peeps up the steps
        if (ride->type == RIDE_TYPE_MOTION_SIMULATOR)
        {
            actionZ = ride->stations[current_ride_station].Height * 8 + 2;

            if (waypoint == 2)
            {
                xy_distance -= 12;
                if (xy_distance < 0)
                    xy_distance = 0;

                if (xy_distance <= 15)
                {
                    actionZ += 15 - xy_distance;
                }
            }
        }
        else
        {
            actionZ = z;
        }
        MoveTo((*loc).x, (*loc).y, actionZ);
        return;
    }

    if (waypoint == 2)
    {
        sub_state = PEEP_RIDE_ENTER_VEHICLE;
        return;
    }

    waypoint++;
    // This is incrementing the actual peep waypoint
    var_37++;

    rct_vehicle* vehicle = GET_VEHICLE(ride->vehicles[current_train]);

    CoordsXY targetLoc;
    targetLoc.x = ride->stations[current_ride_station].Start.x * 32 + 16;
    targetLoc.y = ride->stations[current_ride_station].Start.y * 32 + 16;

    if (ride->type == RIDE_TYPE_ENTERPRISE)
    {
        targetLoc.x = vehicle->x;
        targetLoc.y = vehicle->y;
    }

    rct_ride_entry* ride_entry = get_ride_entry(vehicle->ride_subtype);
    if (ride_entry == nullptr)
    {
        return;
    }

    rct_ride_entry_vehicle* vehicle_type = &ride_entry->vehicles[vehicle->vehicle_type];
    Guard::Assert(waypoint < 3);
    targetLoc.x += vehicle_type->peep_loading_waypoints[var_37 / 4][waypoint].x;
    targetLoc.y += vehicle_type->peep_loading_waypoints[var_37 / 4][waypoint].y;

    destination_x = targetLoc.x;
    destination_y = targetLoc.y;
}

/**
 *
 *  rct2: 0x0069357D
 */
void Guest::UpdateRideApproachExitWaypoints()
{
    auto ride = get_ride(current_ride);
    if (ride == nullptr)
        return;

    int16_t xy_distance;

    if (auto loc = UpdateAction(xy_distance))
    {
        int16_t actionZ;
        if (ride->type == RIDE_TYPE_MOTION_SIMULATOR)
        {
            actionZ = ride->stations[current_ride_station].Height * 8 + 2;

            if ((var_37 & 3) == 1)
            {
                if (xy_distance > 15)
                    xy_distance = 15;

                actionZ += xy_distance;
            }
        }
        else
        {
            actionZ = z;
        }
        MoveTo((*loc).x, (*loc).y, actionZ);
        return;
    }

    if ((var_37 & 3) != 0)
    {
        if ((var_37 & 3) == 3)
        {
            peep_update_ride_prepare_for_exit(this);
            return;
        }

        var_37--;
        rct_vehicle* vehicle = GET_VEHICLE(ride->vehicles[current_train]);
        CoordsXY targetLoc;
        targetLoc.x = ride->stations[current_ride_station].Start.x * 32 + 16;
        targetLoc.y = ride->stations[current_ride_station].Start.y * 32 + 16;

        if (ride->type == RIDE_TYPE_ENTERPRISE)
        {
            targetLoc.x = vehicle->x;
            targetLoc.y = vehicle->y;
        }

        rct_ride_entry* rideEntry = get_ride_entry(vehicle->ride_subtype);
        rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[vehicle->vehicle_type];

        Guard::Assert((var_37 & 3) < 3);
        targetLoc.x += vehicleEntry->peep_loading_waypoints[var_37 / 4][var_37 & 3].x;
        targetLoc.y += vehicleEntry->peep_loading_waypoints[var_37 / 4][var_37 & 3].y;

        destination_x = targetLoc.x;
        destination_y = targetLoc.y;
        return;
    }

    var_37 |= 3;

    auto exit = ride_get_exit_location(ride, current_ride_station);
    CoordsXY targetLoc = { exit.x * 32 + 16, exit.y * 32 + 16 };
    uint8_t exit_direction = direction_reverse(exit.direction);

    int16_t x_shift = word_981D6C[exit_direction].x;
    int16_t y_shift = word_981D6C[exit_direction].y;

    int16_t shift_multiplier = 20;

    auto rideEntry = get_ride_entry(ride->subtype);
    if (rideEntry != nullptr)
    {
        auto vehicleEntry = &rideEntry->vehicles[rideEntry->default_vehicle];
        if (vehicleEntry->flags & (VEHICLE_ENTRY_FLAG_CHAIRLIFT | VEHICLE_ENTRY_FLAG_GO_KART))
        {
            shift_multiplier = 32;
        }
    }

    x_shift *= shift_multiplier;
    y_shift *= shift_multiplier;

    targetLoc.x -= x_shift;
    targetLoc.y -= y_shift;

    destination_x = targetLoc.x;
    destination_y = targetLoc.y;
}

/**
 *
 *  rct2: 0x006927B3
 */
void Guest::UpdateRideApproachSpiralSlide()
{
    auto ride = get_ride(current_ride);
    if (ride == nullptr)
        return;

    if (auto loc = UpdateAction())
    {
        MoveTo((*loc).x, (*loc).y, z);
        return;
    }

    uint8_t waypoint = var_37 & 3;

    if (waypoint == 3)
    {
        sub_state = 15;
        destination_x = 0;
        destination_y = 0;
        var_37 = (var_37 / 4) & 0xC;
        MoveTo(LOCATION_NULL, y, z);
        return;
    }
    else if (waypoint == 2)
    {
        bool lastRide = false;
        if (ride->status != RIDE_STATUS_OPEN)
            lastRide = true;
        else if (current_car++ != 0)
        {
            if (ride->mode == RIDE_MODE_SINGLE_RIDE_PER_ADMISSION)
                lastRide = true;
            if ((uint8_t)(current_car - 1) > (scenario_rand() & 0xF))
                lastRide = true;
        }

        if (lastRide)
        {
            auto exit = ride_get_exit_location(ride, current_ride_station);
            waypoint = 1;
            var_37 = (exit.direction * 4) | (var_37 & 0x30) | waypoint;
            CoordsXY targetLoc;
            targetLoc.x = ride->stations[current_ride_station].Start.x * 32;
            targetLoc.y = ride->stations[current_ride_station].Start.y * 32;

            assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
            const CoordsXY slidePlatformDestination = SpiralSlideWalkingPath[var_37];

            targetLoc += slidePlatformDestination;

            destination_x = targetLoc.x;
            destination_y = targetLoc.y;
            sub_state = PEEP_RIDE_LEAVE_SPIRAL_SLIDE;
            return;
        }
    }
    waypoint++;
    // Actually increment the real peep waypoint
    var_37++;

    CoordsXY targetLoc;
    targetLoc.x = ride->stations[current_ride_station].Start.x * 32;
    targetLoc.y = ride->stations[current_ride_station].Start.y * 32;

    assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
    const CoordsXY slidePlatformDestination = SpiralSlideWalkingPath[var_37];

    targetLoc += slidePlatformDestination;

    destination_x = targetLoc.x;
    destination_y = targetLoc.y;
}

/** rct2: 0x00981F0C, 0x00981F0E */
static constexpr const CoordsXY _SpiralSlideEnd[] = {
    { 25, 56 },
    { 56, 7 },
    { 7, -24 },
    { -24, 25 },
};

/** rct2: 0x00981F1C, 0x00981F1E */
static constexpr const CoordsXY _SpiralSlideEndWaypoint[] = {
    { 8, 56 },
    { 56, 24 },
    { 24, -24 },
    { -24, 8 },
};

/**
 *
 *  rct2: 0x00692D83
 */
void Guest::UpdateRideOnSpiralSlide()
{
    auto ride = get_ride(current_ride);
    if (ride == nullptr || ride->type != RIDE_TYPE_SPIRAL_SLIDE)
        return;

    if ((var_37 & 3) == 0)
    {
        switch (destination_x)
        {
            case 0:
                destination_y++;
                if (destination_y >= 30)
                    destination_x++;
                return;
            case 1:
                if (ride->slide_in_use != 0)
                    return;

                ride->slide_in_use++;
                ride->slide_peep = sprite_index;
                ride->slide_peep_t_shirt_colour = tshirt_colour;
                ride->spiral_slide_progress = 0;
                destination_x++;
                return;
            case 2:
                return;
            case 3:
            {
                int16_t newX = ride->stations[current_ride_station].Start.x * 32;
                int16_t newY = ride->stations[current_ride_station].Start.y * 32;
                uint8_t dir = (var_37 / 4) & 3;

                // Set the location that the peep walks to go on slide again
                destination_x = newX + _SpiralSlideEndWaypoint[dir].x;
                destination_y = newY + _SpiralSlideEndWaypoint[dir].y;

                // Move the peep sprite to just at the end of the slide
                newX += _SpiralSlideEnd[dir].x;
                newY += _SpiralSlideEnd[dir].y;

                MoveTo(newX, newY, z);

                sprite_direction = (var_37 & 0xC) * 2;

                var_37++;
                return;
            }
            default:
                return;
        }
    }

    if (auto loc = UpdateAction())
    {
        MoveTo((*loc).x, (*loc).y, z);
        return;
    }

    uint8_t waypoint = 2;
    var_37 = (var_37 * 4 & 0x30) + waypoint;

    CoordsXY targetLoc;
    targetLoc.x = ride->stations[current_ride_station].Start.x * 32;
    targetLoc.y = ride->stations[current_ride_station].Start.y * 32;

    assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
    const CoordsXY slidePlatformDestination = SpiralSlideWalkingPath[var_37];

    targetLoc += slidePlatformDestination;

    destination_x = targetLoc.x;
    destination_y = targetLoc.y;
    sub_state = PEEP_RIDE_APPROACH_SPIRAL_SLIDE;
}

/**
 *
 *  rct2: 0x00692C6B
 */
void Guest::UpdateRideLeaveSpiralSlide()
{
    // Iterates through the spiral slide waypoints until it reaches
    // waypoint 0. Then it readies to leave the ride by the entrance.
    if (auto loc = UpdateAction())
    {
        MoveTo((*loc).x, (*loc).y, z);
        return;
    }

    auto ride = get_ride(current_ride);
    if (ride == nullptr)
        return;

    uint8_t waypoint = var_37 & 3;

    if (waypoint != 0)
    {
        if (waypoint == 3)
        {
            peep_update_ride_prepare_for_exit(this);
            return;
        }

        waypoint--;
        // Actually decrement the peep waypoint
        var_37--;
        CoordsXY targetLoc;
        targetLoc.x = ride->stations[current_ride_station].Start.x * 32;
        targetLoc.y = ride->stations[current_ride_station].Start.y * 32;

        assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
        const CoordsXY slidePlatformDestination = SpiralSlideWalkingPath[var_37];

        targetLoc += slidePlatformDestination;

        destination_x = targetLoc.x;
        destination_y = targetLoc.y;
        return;
    }
    waypoint = 3;
    // Actually force the final waypoint
    var_37 |= 3;

    auto exit = ride_get_exit_location(ride, current_ride_station);
    CoordsXY targetLoc{ exit.x * 32 + 16, exit.y * 32 + 16 };

    int16_t xShift = word_981D6C[direction_reverse(exit.direction)].x;
    int16_t yShift = word_981D6C[direction_reverse(exit.direction)].y;

    int16_t shiftMultiplier = 20;

    xShift *= shiftMultiplier;
    yShift *= shiftMultiplier;

    targetLoc.x -= xShift;
    targetLoc.y -= yShift;

    destination_x = targetLoc.x;
    destination_y = targetLoc.y;
}

/** rct2: 0x00981FE4 */
static constexpr const uint8_t _MazeGetNewDirectionFromEdge[][4] = {
    { 15, 7, 15, 7 },
    { 11, 3, 11, 3 },
    { 7, 15, 7, 15 },
    { 3, 11, 3, 11 },
};

/** rct2: 0x00981FF4 */
static constexpr const uint8_t _MazeCurrentDirectionToOpenHedge[][4] = {
    { 1, 2, 14, 0 },
    { 4, 5, 6, 2 },
    { 6, 8, 9, 10 },
    { 14, 10, 12, 13 },
};

/**
 *
 *  rct2: 0x00692A83
 */
void Guest::UpdateRideMazePathfinding()
{
    if (auto loc = UpdateAction())
    {
        MoveTo((*loc).x, (*loc).y, z);
        return;
    }

    auto ride = get_ride(current_ride);
    if (ride == nullptr)
        return;

    if (var_37 == 16)
    {
        peep_update_ride_prepare_for_exit(this);
        return;
    }

    if (action >= PEEP_ACTION_NONE_1)
    {
        if (energy > 64 && (scenario_rand() & 0xFFFF) <= 2427)
        {
            action = PEEP_ACTION_JUMP;
            action_frame = 0;
            action_sprite_image_offset = 0;
            UpdateCurrentActionSpriteType();
        }
    }

    CoordsXY targetLoc = { destination_x & 0xFFE0, destination_y & 0xFFE0 };

    int16_t stationHeight = ride->stations[0].Height;

    // Find the station track element
    auto trackElement = map_get_track_element_at(targetLoc.x, targetLoc.y, stationHeight);
    if (trackElement == nullptr)
    {
        return;
    }
    uint16_t mazeEntry = trackElement->GetMazeEntry();
    uint16_t openHedges = 0;
    // var_37 is 3, 7, 11 or 15

    if (mazeEntry & (1 << _MazeCurrentDirectionToOpenHedge[var_37 / 4][3]))
    {
        openHedges = 1;
    }
    openHedges <<= 1;
    if (mazeEntry & (1 << _MazeCurrentDirectionToOpenHedge[var_37 / 4][2]))
    {
        openHedges |= 1;
    }
    openHedges <<= 1;
    if (mazeEntry & (1 << _MazeCurrentDirectionToOpenHedge[var_37 / 4][1]))
    {
        openHedges |= 1;
    }
    openHedges <<= 1;
    if (mazeEntry & (1 << _MazeCurrentDirectionToOpenHedge[var_37 / 4][0]))
    {
        openHedges |= 1;
    }

    openHedges ^= 0xF;
    if (openHedges == 0)
        return;

    uint8_t mazeLastEdge = direction_reverse(maze_last_edge);
    openHedges &= ~(1 << mazeLastEdge);
    if (openHedges == 0)
        openHedges |= (1 << mazeLastEdge);

    uint8_t chosenEdge = scenario_rand() & 0x3;
    while (!(openHedges & (1 << chosenEdge)))
    {
        chosenEdge = (chosenEdge + 1) & 3;
    }

    targetLoc.x = CoordsDirectionDelta[chosenEdge].x / 2;
    targetLoc.y = CoordsDirectionDelta[chosenEdge].y / 2;

    targetLoc.x += destination_x;
    targetLoc.y += destination_y;

    enum class maze_type
    {
        invalid,
        hedge,
        entrance_or_exit
    };
    maze_type mazeType = maze_type::invalid;

    auto tileElement = map_get_first_element_at(targetLoc.x / 32, targetLoc.y / 32);
    if (tileElement == nullptr)
        return;
    do
    {
        if (stationHeight != tileElement->base_height)
            continue;

        if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
        {
            mazeType = maze_type::hedge;
            break;
        }

        if (tileElement->GetType() == TILE_ELEMENT_TYPE_ENTRANCE
            && tileElement->AsEntrance()->GetEntranceType() == ENTRANCE_TYPE_RIDE_EXIT)
        {
            mazeType = maze_type::entrance_or_exit;
            break;
        }
    } while (!(tileElement++)->IsLastForTile());

    switch (mazeType)
    {
        case maze_type::invalid:
            maze_last_edge++;
            maze_last_edge &= 3;
            return;
        case maze_type::hedge:
            destination_x = targetLoc.x;
            destination_y = targetLoc.y;

            var_37 = _MazeGetNewDirectionFromEdge[var_37 / 4][chosenEdge];
            maze_last_edge = chosenEdge;
            break;
        case maze_type::entrance_or_exit:
            targetLoc.x = destination_x;
            targetLoc.y = destination_y;
            if (chosenEdge & 1)
            {
                targetLoc.x &= 0xFFE0;
                targetLoc.x += 16;
            }
            else
            {
                targetLoc.y &= 0xFFE0;
                targetLoc.y += 16;
            }
            destination_x = targetLoc.x;
            destination_y = targetLoc.y;
            var_37 = 16;
            maze_last_edge = chosenEdge;
            break;
    }

    if (auto loc = UpdateAction())
    {
        MoveTo((*loc).x, (*loc).y, z);
        return;
    }
}

/**
 *
 *  rct2: 0x006938D2
 */
void Guest::UpdateRideLeaveExit()
{
    auto ride = get_ride(current_ride);

    if (auto loc = UpdateAction())
    {
        if (ride != nullptr)
        {
            MoveTo((*loc).x, (*loc).y, ride->stations[current_ride_station].Height * 8);
        }
        return;
    }

    OnExitRide(current_ride);

    if (ride != nullptr && (peep_flags & PEEP_FLAGS_TRACKING))
    {
        auto nameArgLen = FormatNameTo(gCommonFormatArgs);
        ride->FormatNameTo(gCommonFormatArgs + nameArgLen);

        if (gConfigNotifications.guest_left_ride)
        {
            news_item_add_to_queue(NEWS_ITEM_PEEP_ON_RIDE, STR_PEEP_TRACKING_LEFT_RIDE_X, sprite_index);
        }
    }

    interaction_ride_index = RIDE_ID_NULL;
    SetState(PEEP_STATE_FALLING);

    CoordsXY targetLoc = { x, y };

    // Find the station track element
    TileElement* tileElement = map_get_first_element_at(targetLoc.x / 32, targetLoc.y / 32);
    if (tileElement == nullptr)
        return;
    do
    {
        if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
            continue;

        int16_t height = map_height_from_slope(
            targetLoc, tileElement->AsPath()->GetSlopeDirection(), tileElement->AsPath()->IsSloped());
        height += tileElement->base_height * 8;

        int16_t z_diff = z - height;
        if (z_diff > 0 || z_diff < -16)
            continue;

        MoveTo(x, y, height);
        return;
    } while (!(tileElement++)->IsLastForTile());
}

/**
 *
 *  rct2: 0x0069299C
 */
void Guest::UpdateRideShopApproach()
{
    if (auto loc = UpdateAction())
    {
        MoveTo((*loc).x, (*loc).y, z);
        return;
    }

    sub_state = PEEP_SHOP_INTERACT;
}

/**
 *
 *  rct2: 0x006929BB
 */
void Guest::UpdateRideShopInteract()
{
    auto ride = get_ride(current_ride);
    if (ride == nullptr)
        return;

    const int16_t tileCenterX = next_x + 16;
    const int16_t tileCenterY = next_y + 16;
    if (ride->type == RIDE_TYPE_FIRST_AID)
    {
        if (nausea <= 35)
        {
            sub_state = PEEP_SHOP_LEAVE;

            destination_x = tileCenterX;
            destination_y = tileCenterY;
            destination_tolerance = 3;
            happiness_target = std::min(happiness_target + 30, PEEP_MAX_HAPPINESS);
            happiness = happiness_target;
        }
        else
        {
            nausea--;
            nausea_target = nausea;
        }
        return;
    }

    if (toilet != 0)
    {
        toilet--;
        return;
    }

    // Do not play toilet flush sound on title screen as it's considered loud and annoying
    if (!(gScreenFlags & SCREEN_FLAGS_TITLE_DEMO))
    {
        audio_play_sound_at_location(SoundId::ToiletFlush, { x, y, z });
    }

    sub_state = PEEP_SHOP_LEAVE;

    destination_x = tileCenterX;
    destination_y = tileCenterY;
    destination_tolerance = 3;

    happiness_target = std::min(happiness_target + 30, PEEP_MAX_HAPPINESS);
    happiness = happiness_target;
    StopPurchaseThought(ride->type);
}

/**
 *
 *  rct2: 0x00692935
 */
void Guest::UpdateRideShopLeave()
{
    if (auto loc = UpdateAction())
    {
        MoveTo((*loc).x, (*loc).y, z);

        if ((x & 0xFFE0) != next_x)
            return;
        if ((y & 0xFFE0) != next_y)
            return;
    }

    SetState(PEEP_STATE_WALKING);

    auto ride = get_ride(current_ride);
    if (ride != nullptr)
    {
        ride->total_customers++;
        ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_CUSTOMER;
        ride_update_satisfaction(ride, happiness / 64);
    }
}

void Guest::UpdateGuest()
{
    switch (state)
    {
        case PEEP_STATE_QUEUING_FRONT:
            UpdateRide();
            break;
        case PEEP_STATE_LEAVING_RIDE:
            UpdateRide();
            break;
        case PEEP_STATE_WALKING:
            UpdateWalking();
            break;
        case PEEP_STATE_QUEUING:
            UpdateQueuing();
            break;
        case PEEP_STATE_ENTERING_RIDE:
            UpdateRide();
            break;
        case PEEP_STATE_SITTING:
            UpdateSitting();
            break;
        case PEEP_STATE_ENTERING_PARK:
            UpdateEnteringPark();
            break;
        case PEEP_STATE_LEAVING_PARK:
            UpdateLeavingPark();
            break;
        case PEEP_STATE_BUYING:
            UpdateBuying();
            break;
        case PEEP_STATE_WATCHING:
            UpdateWatching();
            break;
        case PEEP_STATE_USING_BIN:
            UpdateUsingBin();
            break;
        default:
            // TODO reset to default state
            assert(false);
            break;
    }
}

/**
 *
 *  rct2: 0x691A30
 * Used by entering_ride and queueing_front */
void Guest::UpdateRide()
{
    next_flags &= ~PEEP_NEXT_FLAG_IS_SLOPED;

    switch (sub_state)
    {
        case PEEP_RIDE_AT_ENTRANCE:
            UpdateRideAtEntrance();
            break;
        case PEEP_RIDE_IN_ENTRANCE:
            UpdateRideAdvanceThroughEntrance();
            break;
        case PEEP_RIDE_FREE_VEHICLE_CHECK:
            UpdateRideFreeVehicleCheck();
            break;
        case PEEP_RIDE_LEAVE_ENTRANCE:
            UpdateRideAdvanceThroughEntrance();
            break;
        case PEEP_RIDE_APPROACH_VEHICLE:
            UpdateRideApproachVehicle();
            break;
        case PEEP_RIDE_ENTER_VEHICLE:
            UpdateRideEnterVehicle();
            break;
        case PEEP_RIDE_ON_RIDE:
            // No action, on ride.
            break;
        case PEEP_RIDE_LEAVE_VEHICLE:
            UpdateRideLeaveVehicle();
            break;
        case PEEP_RIDE_APPROACH_EXIT:
            UpdateRideApproachExit();
            break;
        case PEEP_RIDE_IN_EXIT:
            UpdateRideInExit();
            break;
        case PEEP_RIDE_APPROACH_VEHICLE_WAYPOINTS:
            UpdateRideApproachVehicleWaypoints();
            break;
        case PEEP_RIDE_APPROACH_EXIT_WAYPOINTS:
            UpdateRideApproachExitWaypoints();
            break;
        case PEEP_RIDE_APPROACH_SPIRAL_SLIDE:
            UpdateRideApproachSpiralSlide();
            break;
        case PEEP_RIDE_ON_SPIRAL_SLIDE:
            UpdateRideOnSpiralSlide();
            break;
        case PEEP_RIDE_LEAVE_SPIRAL_SLIDE:
            UpdateRideLeaveSpiralSlide();
            break;
        case PEEP_RIDE_MAZE_PATHFINDING:
            UpdateRideMazePathfinding();
            break;
        case PEEP_RIDE_LEAVE_EXIT:
            UpdateRideLeaveExit();
            break;
        case PEEP_SHOP_APPROACH:
            UpdateRideShopApproach();
            break;
        case PEEP_SHOP_INTERACT:
            UpdateRideShopInteract();
            break;
        case PEEP_SHOP_LEAVE:
            UpdateRideShopLeave();
            break;
        default:
            // Invalid peep sub-state
            assert(false);
            break;
    }
}

static void peep_update_walking_break_scenery(Peep* peep);
static bool peep_find_ride_to_look_at(Peep* peep, uint8_t edge, uint8_t* rideToView, uint8_t* rideSeatToView);

/**
 *
 *  rct2: 0x0069030A
 */
void Guest::UpdateWalking()
{
    if (!CheckForPath())
        return;

    if (peep_flags & PEEP_FLAGS_WAVING)
    {
        if (action >= PEEP_ACTION_NONE_1)
        {
            if ((0xFFFF & scenario_rand()) < 936)
            {
                action = PEEP_ACTION_WAVE_2;
                action_frame = 0;
                action_sprite_image_offset = 0;

                UpdateCurrentActionSpriteType();
            }
        }
    }

    if (peep_flags & PEEP_FLAGS_PHOTO)
    {
        if (action >= PEEP_ACTION_NONE_1)
        {
            if ((0xFFFF & scenario_rand()) < 936)
            {
                action = PEEP_ACTION_TAKE_PHOTO;
                action_frame = 0;
                action_sprite_image_offset = 0;

                UpdateCurrentActionSpriteType();
            }
        }
    }

    if (peep_flags & PEEP_FLAGS_PAINTING)
    {
        if (action >= PEEP_ACTION_NONE_1)
        {
            if ((0xFFFF & scenario_rand()) < 936)
            {
                action = PEEP_ACTION_DRAW_PICTURE;
                action_frame = 0;
                action_sprite_image_offset = 0;

                UpdateCurrentActionSpriteType();
            }
        }
    }

    if (peep_flags & PEEP_FLAGS_LITTER)
    {
        if (!GetNextIsSurface())
        {
            if ((0xFFFF & scenario_rand()) <= 4096)
            {
                static constexpr const uint8_t litter_types[] = {
                    LITTER_TYPE_EMPTY_CAN,
                    LITTER_TYPE_RUBBISH,
                    LITTER_TYPE_EMPTY_BURGER_BOX,
                    LITTER_TYPE_EMPTY_CUP,
                };
                int32_t litterType = litter_types[scenario_rand() & 0x3];
                int32_t litterX = x + (scenario_rand() & 0x7) - 3;
                int32_t litterY = y + (scenario_rand() & 0x7) - 3;
                int32_t litterDirection = (scenario_rand() & 0x3);

                litter_create(litterX, litterY, z, litterDirection, litterType);
            }
        }
    }
    else if (HasEmptyContainer())
    {
        if ((!GetNextIsSurface()) && ((uint32_t)(sprite_index & 0x1FF) == (gCurrentTicks & 0x1FF))
            && ((0xFFFF & scenario_rand()) <= 4096))
        {
            uint8_t pos_stnd = 0;
            for (int32_t container = HasEmptyContainerStandardFlag(); pos_stnd < 32; pos_stnd++)
                if (container & (1u << pos_stnd))
                    break;

            int32_t litterType = 0;

            if (pos_stnd != 32)
            {
                item_standard_flags &= ~(1u << pos_stnd);
                litterType = item_standard_litter[pos_stnd];
            }
            else
            {
                uint8_t pos_extr = 0;
                for (int32_t container = HasEmptyContainerExtraFlag(); pos_extr < 32; pos_extr++)
                    if (container & (1u << pos_extr))
                        break;
                item_extra_flags &= ~(1u << pos_extr);
                litterType = item_extra_litter[pos_extr];
            }

            window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY;
            UpdateSpriteType();

            int32_t litterX = x + (scenario_rand() & 0x7) - 3;
            int32_t litterY = y + (scenario_rand() & 0x7) - 3;
            int32_t litterDirection = (scenario_rand() & 0x3);

            litter_create(litterX, litterY, z, litterDirection, litterType);
        }
    }

    // Check if vehicle is blocking the destination tile
    auto curPos = TileCoordsXYZ(CoordsXYZ{ x, y, z });
    auto dstPos = TileCoordsXYZ(CoordsXY{ destination_x, destination_y }, next_z);
    if (curPos.x != dstPos.x || curPos.y != dstPos.y)
    {
        if (footpath_is_blocked_by_vehicle(dstPos))
        {
            // Wait for vehicle to pass
            return;
        }
    }

    uint8_t pathingResult;
    PerformNextAction(pathingResult);
    if (!(pathingResult & PATHING_DESTINATION_REACHED))
        return;

    if (GetNextIsSurface())
    {
        auto surfaceElement = map_get_surface_element_at({ next_x, next_y });

        int32_t water_height = surfaceElement->GetWaterHeight();
        if (water_height)
        {
            water_height *= 16;
            MoveTo(x, y, water_height);
            SetState(PEEP_STATE_FALLING);
            return;
        }
    }

    CheckIfLost();
    CheckCantFindRide();
    CheckCantFindExit();

    if (UpdateWalkingFindBench())
        return;

    if (UpdateWalkingFindBin())
        return;

    peep_update_walking_break_scenery(this);

    if (state != PEEP_STATE_WALKING)
        return;

    if (peep_flags & PEEP_FLAGS_LEAVING_PARK)
        return;

    if (nausea > 140)
        return;

    if (happiness < 120)
        return;

    if (toilet > 140)
        return;

    uint16_t chance = HasFood() ? 13107 : 2849;

    if ((scenario_rand() & 0xFFFF) > chance)
        return;

    if (GetNextIsSurface() || GetNextIsSloped())
        return;

    TileElement* tileElement = map_get_first_element_at(next_x / 32, next_y / 32);
    if (tileElement == nullptr)
        return;

    for (;; tileElement++)
    {
        if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
        {
            if (next_z == tileElement->base_height)
                break;
        }
        if (tileElement->IsLastForTile())
        {
            return;
        }
    }

    int32_t positions_free = 15;

    if (tileElement->AsPath()->HasAddition())
    {
        if (!tileElement->AsPath()->AdditionIsGhost())
        {
            rct_scenery_entry* sceneryEntry = tileElement->AsPath()->GetAdditionEntry();
            if (sceneryEntry == nullptr)
            {
                return;
            }

            if (!(sceneryEntry->path_bit.flags & PATH_BIT_FLAG_IS_BENCH))
                positions_free = 9;
        }
    }

    int32_t edges = (tileElement->AsPath()->GetEdges()) ^ 0xF;
    if (edges == 0)
        return;

    uint8_t chosen_edge = scenario_rand() & 0x3;

    for (; !(edges & (1 << chosen_edge));)
        chosen_edge = (chosen_edge + 1) & 3;

    uint8_t ride_to_view, ride_seat_to_view;
    if (!peep_find_ride_to_look_at(this, chosen_edge, &ride_to_view, &ride_seat_to_view))
        return;

    // Check if there is a peep watching (and if there is place for us)
    uint16_t sprite_id = sprite_get_first_in_quadrant(x, y);
    for (rct_sprite* sprite; sprite_id != SPRITE_INDEX_NULL; sprite_id = sprite->generic.next_in_quadrant)
    {
        sprite = get_sprite(sprite_id);

        if (sprite->generic.linked_list_index != SPRITE_LIST_PEEP)
            continue;

        if (sprite->peep.state != PEEP_STATE_WATCHING)
            continue;

        if (z != sprite->peep.z)
            continue;

        if ((sprite->peep.var_37 & 0x3) != chosen_edge)
            continue;

        positions_free &= ~(1 << ((sprite->peep.var_37 & 0x1C) >> 2));
    }

    if (!positions_free)
        return;

    uint8_t chosen_position = scenario_rand() & 0x3;

    for (; !(positions_free & (1 << chosen_position));)
        chosen_position = (chosen_position + 1) & 3;

    current_ride = ride_to_view;
    current_seat = ride_seat_to_view;
    var_37 = chosen_edge | (chosen_position << 2);

    SetState(PEEP_STATE_WATCHING);
    sub_state = 0;

    int32_t destX = (x & 0xFFE0) + _WatchingPositionOffsets[var_37 & 0x1F].x;
    int32_t destY = (y & 0xFFE0) + _WatchingPositionOffsets[var_37 & 0x1F].y;

    destination_x = destX;
    destination_y = destY;
    destination_tolerance = 3;

    if (current_seat & 1)
    {
        InsertNewThought(PEEP_THOUGHT_TYPE_NEW_RIDE, PEEP_THOUGHT_ITEM_NONE);
    }
    if (current_ride == RIDE_ID_NULL)
    {
        InsertNewThought(PEEP_THOUGHT_TYPE_SCENERY, PEEP_THOUGHT_ITEM_NONE);
    }
}

/**
 *
 *  rct2: 0x69185D
 */
void Guest::UpdateQueuing()
{
    if (!CheckForPath())
    {
        RemoveFromQueue();
        return;
    }
    auto ride = get_ride(current_ride);
    if (ride == nullptr || ride->status != RIDE_STATUS_OPEN)
    {
        RemoveFromQueue();
        SetState(PEEP_STATE_1);
        return;
    }

    if (sub_state != 10)
    {
        bool is_front = true;
        if (next_in_queue != SPRITE_INDEX_NULL)
        {
            // Fix #4819: Occasionally the peep->next_in_queue is incorrectly set
            // to prevent this from causing the peeps to enter a loop
            // first check if the next in queue is actually nearby
            // if they are not then it's safe to assume that this is
            // the front of the queue.
            Peep* next_peep = GET_PEEP(next_in_queue);
            if (abs(next_peep->x - x) < 32 && abs(next_peep->y - y) < 32)
            {
                is_front = false;
            }
        }

        if (is_front)
        {
            // Happens every time peep goes onto ride.
            destination_tolerance = 0;
            SetState(PEEP_STATE_QUEUING_FRONT);
            sub_state = PEEP_RIDE_AT_ENTRANCE;
            return;
        }

        // Give up queueing for the ride
        sprite_direction ^= (1 << 4);
        Invalidate();
        RemoveFromQueue();
        SetState(PEEP_STATE_1);
        return;
    }

    uint8_t pathingResult;
    PerformNextAction(pathingResult);
    if (action < PEEP_ACTION_NONE_1)
        return;
    if (sprite_type == PEEP_SPRITE_TYPE_NORMAL)
    {
        if (time_in_queue >= 2000 && (0xFFFF & scenario_rand()) <= 119)
        {
            // Eat Food/Look at watch
            action = PEEP_ACTION_EAT_FOOD;
            action_frame = 0;
            action_sprite_image_offset = 0;
            UpdateCurrentActionSpriteType();
        }
        if (time_in_queue >= 3500 && (0xFFFF & scenario_rand()) <= 93)
        {
            // Create the I have been waiting in line ages thought
            InsertNewThought(PEEP_THOUGHT_TYPE_QUEUING_AGES, current_ride);
        }
    }
    else
    {
        if (!(time_in_queue & 0x3F) && action == PEEP_ACTION_NONE_1 && next_action_sprite_type == 2)
        {
            switch (sprite_type)
            {
                case PEEP_SPRITE_TYPE_ICE_CREAM:
                case PEEP_SPRITE_TYPE_CHIPS:
                case PEEP_SPRITE_TYPE_BURGER:
                case PEEP_SPRITE_TYPE_DRINK:
                case PEEP_SPRITE_TYPE_CANDYFLOSS:
                case PEEP_SPRITE_TYPE_PIZZA:
                case PEEP_SPRITE_TYPE_POPCORN:
                case PEEP_SPRITE_TYPE_HOT_DOG:
                case PEEP_SPRITE_TYPE_TENTACLE:
                case PEEP_SPRITE_TYPE_TOFFEE_APPLE:
                case PEEP_SPRITE_TYPE_DOUGHNUT:
                case PEEP_SPRITE_TYPE_COFFEE:
                case PEEP_SPRITE_TYPE_CHICKEN:
                case PEEP_SPRITE_TYPE_LEMONADE:
                case PEEP_SPRITE_TYPE_PRETZEL:
                case PEEP_SPRITE_TYPE_SU_JONGKWA:
                case PEEP_SPRITE_TYPE_JUICE:
                case PEEP_SPRITE_TYPE_FUNNEL_CAKE:
                case PEEP_SPRITE_TYPE_NOODLES:
                case PEEP_SPRITE_TYPE_SAUSAGE:
                case PEEP_SPRITE_TYPE_SOUP:
                case PEEP_SPRITE_TYPE_SANDWICH:
                    // Eat food
                    action = PEEP_ACTION_EAT_FOOD;
                    action_frame = 0;
                    action_sprite_image_offset = 0;
                    UpdateCurrentActionSpriteType();
                    break;
                default:
                    break;
            }
        }
    }
    if (time_in_queue < 4300)
        return;

    if (happiness <= 65 && (0xFFFF & scenario_rand()) < 2184)
    {
        // Give up queueing for the ride
        sprite_direction ^= (1 << 4);
        Invalidate();
        RemoveFromQueue();
        SetState(PEEP_STATE_1);
    }
}

/**
 * rct2: 0x691451
 */
void Guest::UpdateEnteringPark()
{
    if (var_37 != 1)
    {
        uint8_t pathingResult;
        PerformNextAction(pathingResult);
        if ((pathingResult & PATHING_OUTSIDE_PARK))
        {
            decrement_guests_heading_for_park();
            peep_sprite_remove(this);
        }
        return;
    }
    if (auto loc = UpdateAction())
    {
        MoveTo((*loc).x, (*loc).y, z);
        return;
    }
    SetState(PEEP_STATE_FALLING);

    outside_of_park = 0;
    time_in_park = gScenarioTicks;
    increment_guests_in_park();
    decrement_guests_heading_for_park();
    auto intent = Intent(INTENT_ACTION_UPDATE_GUEST_COUNT);
    context_broadcast_intent(&intent);
}

/**
 *
 *  rct2: 0x6914CD
 */
void Guest::UpdateLeavingPark()
{
    if (var_37 != 0)
    {
        uint8_t pathingResult;
        PerformNextAction(pathingResult);
        if (!(pathingResult & PATHING_OUTSIDE_PARK))
            return;
        peep_sprite_remove(this);
        return;
    }

    if (auto loc = UpdateAction())
    {
        MoveTo((*loc).x, (*loc).y, z);
        return;
    }

    outside_of_park = 1;
    destination_tolerance = 5;
    decrement_guests_in_park();
    auto intent = Intent(INTENT_ACTION_UPDATE_GUEST_COUNT);
    context_broadcast_intent(&intent);
    var_37 = 1;

    window_invalidate_by_class(WC_GUEST_LIST);
    uint8_t pathingResult;
    PerformNextAction(pathingResult);
    if (!(pathingResult & PATHING_OUTSIDE_PARK))
        return;
    Remove();
}

/**
 *
 *  rct2: 0x6916D6
 */
void Guest::UpdateWatching()
{
    if (sub_state == 0)
    {
        if (!CheckForPath())
            return;
        uint8_t pathingResult;
        PerformNextAction(pathingResult);
        if (!(pathingResult & PATHING_DESTINATION_REACHED))
            return;

        destination_x = x;
        destination_y = y;

        sprite_direction = (var_37 & 3) * 8;

        action = PEEP_ACTION_NONE_1;
        next_action_sprite_type = PEEP_ACTION_SPRITE_TYPE_WATCH_RIDE;

        SwitchNextActionSpriteType();

        sub_state++;

        time_to_stand = std::clamp(((129 - energy) * 16 + 50) / 2, 0, 255);
        UpdateSpriteType();
    }
    else if (sub_state == 1)
    {
        if (action < PEEP_ACTION_NONE_1)
        {
            // 6917F6
            UpdateAction();
            Invalidate();
            if (action != PEEP_ACTION_NONE_2)
                return;
            action = PEEP_ACTION_NONE_1;
        }
        else
        {
            if (HasFood())
            {
                if ((scenario_rand() & 0xFFFF) <= 1310)
                {
                    action = PEEP_ACTION_EAT_FOOD;
                    action_frame = 0;
                    action_sprite_image_offset = 0;
                    UpdateCurrentActionSpriteType();
                    return;
                }
            }

            if ((scenario_rand() & 0xFFFF) <= 655)
            {
                action = PEEP_ACTION_TAKE_PHOTO;
                action_frame = 0;
                action_sprite_image_offset = 0;
                UpdateCurrentActionSpriteType();
                return;
            }

            if ((standing_flags & 1))
            {
                if ((scenario_rand() & 0xFFFF) <= 655)
                {
                    action = PEEP_ACTION_WAVE;
                    action_frame = 0;
                    action_sprite_image_offset = 0;
                    UpdateCurrentActionSpriteType();
                    return;
                }
            }
        }

        standing_flags ^= (1 << 7);
        if (!(standing_flags & (1 << 7)))
            return;

        time_to_stand--;
        if (time_to_stand != 0)
            return;

        SetState(PEEP_STATE_WALKING);
        UpdateSpriteType();
        // Send peep to the centre of current tile.
        destination_x = (x & 0xFFE0) + 16;
        destination_y = (y & 0xFFE0) + 16;
        destination_tolerance = 5;
        UpdateCurrentActionSpriteType();
    }
}

/**
 *
 *  rct2: 0x00691089
 */
void Guest::UpdateUsingBin()
{
    switch (sub_state)
    {
        case PEEP_USING_BIN_WALKING_TO_BIN:
        {
            if (!CheckForPath())
                return;

            uint8_t pathingResult;
            PerformNextAction(pathingResult);
            if (pathingResult & PATHING_DESTINATION_REACHED)
            {
                sub_state = PEEP_USING_BIN_GOING_BACK;
            }
            break;
        }
        case PEEP_USING_BIN_GOING_BACK:
        {
            if (action != PEEP_ACTION_NONE_2)
            {
                UpdateAction();
                Invalidate();
                return;
            }

            TileElement* tileElement = map_get_first_element_at(next_x / 32, next_y / 32);
            if (tileElement == nullptr)
                return;

            for (;; tileElement++)
            {
                if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
                {
                    continue;
                }

                if (tileElement->base_height == next_z)
                    break;

                if (tileElement->IsLastForTile())
                {
                    StateReset();
                    return;
                }
            }

            if (!tileElement->AsPath()->HasAddition())
            {
                StateReset();
                return;
            }

            rct_scenery_entry* sceneryEntry = tileElement->AsPath()->GetAdditionEntry();
            if (!(sceneryEntry->path_bit.flags & PATH_BIT_FLAG_IS_BIN))
            {
                StateReset();
                return;
            }

            if (tileElement->AsPath()->IsBroken())
            {
                StateReset();
                return;
            }

            if (tileElement->AsPath()->AdditionIsGhost())
            {
                StateReset();
                return;
            }

            // Bin selection is one of 4 corners
            uint8_t selected_bin = var_37 * 2;

            // This counts down 2 = No rubbish, 0 = full
            uint8_t space_left_in_bin = 0x3 & (tileElement->AsPath()->GetAdditionStatus() >> selected_bin);
            uint32_t empty_containers = HasEmptyContainerStandardFlag();

            for (uint8_t cur_container = 0; cur_container < 32; cur_container++)
            {
                if (!(empty_containers & (1u << cur_container)))
                    continue;

                if (space_left_in_bin != 0)
                {
                    // OpenRCT2 modification: This previously used
                    // the tick count as a simple random function
                    // switched to scenario_rand as it is more reliable
                    if ((scenario_rand() & 7) == 0)
                        space_left_in_bin--;
                    item_standard_flags &= ~(1 << cur_container);
                    window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY;
                    UpdateSpriteType();
                    continue;
                }
                uint8_t litterType = item_standard_litter[cur_container];

                int32_t litterX = x + (scenario_rand() & 7) - 3;
                int32_t litterY = y + (scenario_rand() & 7) - 3;

                litter_create(litterX, litterY, z, scenario_rand() & 3, litterType);
                item_standard_flags &= ~(1 << cur_container);
                window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY;

                UpdateSpriteType();
            }

            // Original bug: This would clear any rubbish placed by the previous function
            // space_left_in_bin = 0x3 & (tile_element->properties.path.addition_status >> selected_bin);
            empty_containers = HasEmptyContainerExtraFlag();

            for (uint8_t cur_container = 0; cur_container < 32; cur_container++)
            {
                if (!(empty_containers & (1u << cur_container)))
                    continue;

                if (space_left_in_bin != 0)
                {
                    // OpenRCT2 modification: This previously used
                    // the tick count as a simple random function
                    // switched to scenario_rand as it is more reliable
                    if ((scenario_rand() & 7) == 0)
                        space_left_in_bin--;
                    item_extra_flags &= ~(1 << cur_container);
                    window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY;

                    UpdateSpriteType();
                    continue;
                }
                uint8_t litterType = item_extra_litter[cur_container];

                int32_t litterX = x + (scenario_rand() & 7) - 3;
                int32_t litterY = y + (scenario_rand() & 7) - 3;

                litter_create(litterX, litterY, z, scenario_rand() & 3, litterType);
                item_extra_flags &= ~(1 << cur_container);
                window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY;

                UpdateSpriteType();
            }

            uint8_t additionStatus = tileElement->AsPath()->GetAdditionStatus();
            // Place new amount in bin by first clearing the value
            additionStatus &= ~(3 << selected_bin);
            // Then placing the new value.
            additionStatus |= space_left_in_bin << selected_bin;
            tileElement->AsPath()->SetAdditionStatus(additionStatus);

            map_invalidate_tile_zoom0(next_x, next_y, tileElement->base_height << 3, tileElement->clearance_height << 3);
            StateReset();
            break;
        }
        default:
            Guard::Assert(false, "Invalid sub state");
            break;
    }
}

/* Simplifies 0x690582. Returns true if should find bench*/
bool Guest::ShouldFindBench()
{
    if (peep_flags & PEEP_FLAGS_LEAVING_PARK)
    {
        return false;
    }

    if (HasFood())
    {
        if (hunger < 128 || happiness < 128)
        {
            if (!GetNextIsSurface() && !GetNextIsSloped())
            {
                return true;
            }
        }
    }

    if (nausea <= 170 && energy > 50)
    {
        return false;
    }

    return !GetNextIsSurface() && !GetNextIsSloped();
}

/**
 *
 *  rct2: 0x00690582
 * Returns true when the guest wants to sit down and has found a bench to sit on
 */
bool Guest::UpdateWalkingFindBench()
{
    if (!ShouldFindBench())
        return false;

    TileElement* tileElement = map_get_first_element_at(next_x / 32, next_y / 32);
    if (tileElement == nullptr)
        return false;

    for (;; tileElement++)
    {
        if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
        {
            if (next_z == tileElement->base_height)
                break;
        }
        if (tileElement->IsLastForTile())
        {
            return false;
        }
    }

    if (!tileElement->AsPath()->HasAddition())
        return false;
    rct_scenery_entry* sceneryEntry = tileElement->AsPath()->GetAdditionEntry();

    if (sceneryEntry == nullptr || !(sceneryEntry->path_bit.flags & PATH_BIT_FLAG_IS_BENCH))
        return false;

    if (tileElement->AsPath()->IsBroken())
        return false;

    if (tileElement->AsPath()->AdditionIsGhost())
        return false;

    int32_t edges = (tileElement->AsPath()->GetEdges()) ^ 0xF;
    if (edges == 0)
        return false;

    uint8_t chosen_edge = scenario_rand() & 0x3;

    for (; !(edges & (1 << chosen_edge));)
        chosen_edge = (chosen_edge + 1) & 0x3;

    uint16_t sprite_id = sprite_get_first_in_quadrant(x, y);
    uint8_t free_edge = 3;

    // Check if there is no peep sitting in chosen_edge
    for (rct_sprite* sprite; sprite_id != SPRITE_INDEX_NULL; sprite_id = sprite->generic.next_in_quadrant)
    {
        sprite = get_sprite(sprite_id);

        if (sprite->generic.linked_list_index != SPRITE_LIST_PEEP)
            continue;

        if (sprite->peep.state != PEEP_STATE_SITTING)
            continue;

        if (z != sprite->peep.z)
            continue;

        if ((sprite->peep.var_37 & 0x3) != chosen_edge)
            continue;

        free_edge &= ~(1 << ((sprite->peep.var_37 & 0x4) >> 2));
    }

    if (!free_edge)
        return false;

    free_edge ^= 0x3;
    if (!free_edge)
    {
        if (scenario_rand() & 0x8000000)
            free_edge = 1;
    }

    var_37 = ((free_edge & 1) << 2) | chosen_edge;

    SetState(PEEP_STATE_SITTING);

    sub_state = PEEP_SITTING_TRYING_TO_SIT;

    int32_t benchX = (x & 0xFFE0) + BenchUseOffsets[var_37 & 0x7].x;
    int32_t benchY = (y & 0xFFE0) + BenchUseOffsets[var_37 & 0x7].y;

    destination_x = benchX;
    destination_y = benchY;
    destination_tolerance = 3;

    return true;
}

bool Guest::UpdateWalkingFindBin()
{
    auto peep = this;
    if (!peep->HasEmptyContainer())
        return false;

    if (peep->GetNextIsSurface())
        return false;

    TileElement* tileElement = map_get_first_element_at(peep->next_x / 32, peep->next_y / 32);
    if (tileElement == nullptr)
        return false;

    for (;; tileElement++)
    {
        if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
        {
            if (peep->next_z == tileElement->base_height)
                break;
        }
        if (tileElement->IsLastForTile())
        {
            return false;
        }
    }

    if (!tileElement->AsPath()->HasAddition())
        return false;
    rct_scenery_entry* sceneryEntry = tileElement->AsPath()->GetAdditionEntry();
    if (sceneryEntry == nullptr)
    {
        return false;
    }

    if (!(sceneryEntry->path_bit.flags & PATH_BIT_FLAG_IS_BIN))
        return false;

    if (tileElement->AsPath()->IsBroken())
        return false;

    if (tileElement->AsPath()->AdditionIsGhost())
        return false;

    int32_t edges = (tileElement->AsPath()->GetEdges()) ^ 0xF;
    if (edges == 0)
        return false;

    uint8_t chosen_edge = scenario_rand() & 0x3;

    // Note: Bin quantity is inverted 0 = full, 3 = empty
    uint8_t bin_quantities = tileElement->AsPath()->GetAdditionStatus();

    // Rotate the bin to the correct edge. Makes it easier for next calc.
    bin_quantities = ror8(ror8(bin_quantities, chosen_edge), chosen_edge);

    for (uint8_t free_edge = 4; free_edge != 0; free_edge--)
    {
        // If not full
        if (bin_quantities & 0x3)
        {
            if (edges & (1 << chosen_edge))
                break;
        }
        chosen_edge = (chosen_edge + 1) & 0x3;
        bin_quantities = ror8(bin_quantities, 2);
        if ((free_edge - 1) == 0)
            return 0;
    }

    peep->var_37 = chosen_edge;

    peep->SetState(PEEP_STATE_USING_BIN);
    peep->sub_state = PEEP_USING_BIN_WALKING_TO_BIN;

    int32_t binX = (peep->x & 0xFFE0) + BinUseOffsets[peep->var_37 & 0x3].x;
    int32_t binY = (peep->y & 0xFFE0) + BinUseOffsets[peep->var_37 & 0x3].y;

    peep->destination_x = binX;
    peep->destination_y = binY;
    peep->destination_tolerance = 3;

    return true;
}

/**
 *
 *  rct2: 0x00690848
 */
static void peep_update_walking_break_scenery(Peep* peep)
{
    if (gCheatsDisableVandalism)
        return;

    if (!(peep->peep_flags & PEEP_FLAGS_ANGRY))
    {
        if (peep->happiness >= 48)
            return;
        if (peep->energy < 85)
            return;
        if (peep->state != PEEP_STATE_WALKING)
            return;

        if ((peep->litter_count & 0xC0) != 0xC0 && (peep->disgusting_count & 0xC0) != 0xC0)
            return;

        if ((scenario_rand() & 0xFFFF) > 3276)
            return;
    }

    if (peep->GetNextIsSurface())
        return;

    TileElement* tileElement = map_get_first_element_at(peep->next_x / 32, peep->next_y / 32);
    if (tileElement == nullptr)
        return;

    for (;; tileElement++)
    {
        if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
        {
            if (peep->next_z == tileElement->base_height)
                break;
        }
        if (tileElement->IsLastForTile())
        {
            return;
        }
    }

    if (!tileElement->AsPath()->HasAddition())
        return;
    rct_scenery_entry* sceneryEntry = tileElement->AsPath()->GetAdditionEntry();

    if (!(sceneryEntry->path_bit.flags & PATH_BIT_FLAG_BREAKABLE))
        return;

    if (tileElement->AsPath()->IsBroken())
        return;

    if (tileElement->AsPath()->AdditionIsGhost())
        return;

    int32_t edges = tileElement->AsPath()->GetEdges();
    if (edges == 0xF)
        return;

    uint16_t sprite_id = sprite_get_first_in_quadrant(peep->x, peep->y);

    // Check if a peep is already sitting on the bench. If so, do not vandalise it.
    for (rct_sprite* sprite; sprite_id != SPRITE_INDEX_NULL; sprite_id = sprite->generic.next_in_quadrant)
    {
        sprite = get_sprite(sprite_id);

        if ((sprite->generic.linked_list_index != SPRITE_LIST_PEEP) || (sprite->peep.state != PEEP_STATE_SITTING)
            || (peep->z != sprite->peep.z))
        {
            continue;
        }

        return;
    }

    Peep* inner_peep;
    uint16_t sprite_index;

    FOR_ALL_STAFF (sprite_index, inner_peep)
    {
        if (inner_peep->staff_type != STAFF_TYPE_SECURITY)
            continue;

        if (inner_peep->x == LOCATION_NULL)
            continue;

        int32_t x_diff = abs(inner_peep->x - peep->x);
        int32_t y_diff = abs(inner_peep->y - peep->y);

        if (std::max(x_diff, y_diff) < 224)
            return;
    }

    tileElement->AsPath()->SetIsBroken(true);

    map_invalidate_tile_zoom1(peep->next_x, peep->next_y, (tileElement->base_height << 3) + 32, tileElement->base_height << 3);

    peep->angriness = 16;
}

/**
 * rct2: 0x0069101A
 *
 * @return (CF)
 */
static bool peep_should_watch_ride(TileElement* tileElement)
{
    // Ghosts are purely this-client-side and should not cause any interaction,
    // as that may lead to a desync.
    if (network_get_mode() != NETWORK_MODE_NONE)
    {
        if (tileElement->IsGhost())
            return false;
    }

    auto ride = get_ride(tileElement->AsTrack()->GetRideIndex());
    if (ride == nullptr || !ride->IsRide())
    {
        return false;
    }

    // This is most likely to have peeps watch new rides
    if (ride->excitement == RIDE_RATING_UNDEFINED)
    {
        return true;
    }

    if (ride->excitement >= RIDE_RATING(4, 70))
    {
        return true;
    }

    if (ride->intensity >= RIDE_RATING(4, 50))
    {
        return true;
    }

    if (RideData4[ride->type].flags & RIDE_TYPE_FLAG4_INTERESTING_TO_LOOK_AT)
    {
        if ((scenario_rand() & 0xFFFF) > 0x3333)
        {
            return false;
        }
    }
    else if (RideData4[ride->type].flags & RIDE_TYPE_FLAG4_SLIGHTLY_INTERESTING_TO_LOOK_AT)
    {
        if ((scenario_rand() & 0xFFFF) > 0x1000)
        {
            return false;
        }
    }
    else
    {
        return false;
    }

    return true;
}

bool loc_690FD0(Peep* peep, uint8_t* rideToView, uint8_t* rideSeatToView, TileElement* tileElement)
{
    auto ride = get_ride(tileElement->AsTrack()->GetRideIndex());
    if (ride == nullptr)
        return false;

    *rideToView = ride->id;
    if (ride->excitement == RIDE_RATING_UNDEFINED)
    {
        *rideSeatToView = 1;
        if (ride->status != RIDE_STATUS_OPEN)
        {
            if (tileElement->clearance_height > peep->next_z + 8)
            {
                *rideSeatToView |= (1 << 1);
            }

            return true;
        }
    }
    else
    {
        *rideSeatToView = 0;
        if (ride->status == RIDE_STATUS_OPEN && !(ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN))
        {
            if (tileElement->clearance_height > peep->next_z + 8)
            {
                *rideSeatToView = 0x02;
            }

            return true;
        }
    }

    return false;
}

/**
 *
 *  rct2: 0x00690B99
 *
 * @param edge (eax)
 * @param peep (esi)
 * @param[out] rideToView (cl)
 * @param[out] rideSeatToView (ch)
 * @return !CF
 */
static bool peep_find_ride_to_look_at(Peep* peep, uint8_t edge, uint8_t* rideToView, uint8_t* rideSeatToView)
{
    TileElement* tileElement;

    auto surfaceElement = map_get_surface_element_at({ peep->next_x, peep->next_y });

    tileElement = reinterpret_cast<TileElement*>(surfaceElement);
    if (tileElement == nullptr)
    {
        return false;
    }

    do
    {
        // Ghosts are purely this-client-side and should not cause any interaction,
        // as that may lead to a desync.
        if (network_get_mode() != NETWORK_MODE_NONE)
        {
            if (tileElement->IsGhost())
                continue;
        }
        if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
            continue;
        if (tileElement->GetDirection() != edge)
            continue;
        auto wallEntry = tileElement->AsWall()->GetEntry();
        if (wallEntry == nullptr || (wallEntry->wall.flags2 & WALL_SCENERY_2_IS_OPAQUE))
            continue;
        if (peep->next_z + 4 <= tileElement->base_height)
            continue;
        if (peep->next_z + 1 >= tileElement->clearance_height)
            continue;

        return false;
    } while (!(tileElement++)->IsLastForTile());

    uint16_t x = peep->next_x + CoordsDirectionDelta[edge].x;
    uint16_t y = peep->next_y + CoordsDirectionDelta[edge].y;
    if (x > 255 * 32 || y > 255 * 32)
    {
        return false;
    }

    surfaceElement = map_get_surface_element_at({ x, y });

    tileElement = reinterpret_cast<TileElement*>(surfaceElement);
    if (tileElement == nullptr)
    {
        return false;
    }

    do
    {
        // Ghosts are purely this-client-side and should not cause any interaction,
        // as that may lead to a desync.
        if (network_get_mode() != NETWORK_MODE_NONE)
        {
            if (tileElement->IsGhost())
                continue;
        }
        if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
            continue;
        if (direction_reverse(tileElement->GetDirection()) != edge)
            continue;
        auto wallEntry = tileElement->AsWall()->GetEntry();
        if (wallEntry == nullptr || (wallEntry->wall.flags2 & WALL_SCENERY_2_IS_OPAQUE))
            continue;
        // TODO: Check whether this shouldn't be <=, as the other loops use. If so, also extract as loop A.
        if (peep->next_z + 4 >= tileElement->base_height)
            continue;
        if (peep->next_z + 1 >= tileElement->clearance_height)
            continue;

        return false;
    } while (!(tileElement++)->IsLastForTile());

    // TODO: Extract loop B
    tileElement = reinterpret_cast<TileElement*>(surfaceElement);
    do
    {
        // Ghosts are purely this-client-side and should not cause any interaction,
        // as that may lead to a desync.
        if (network_get_mode() != NETWORK_MODE_NONE)
        {
            if (tileElement->IsGhost())
                continue;
        }

        if (tileElement->clearance_height + 1 < peep->next_z)
            continue;
        if (peep->next_z + 6 < tileElement->base_height)
            continue;

        if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
        {
            if (peep_should_watch_ride(tileElement))
            {
                return loc_690FD0(peep, rideToView, rideSeatToView, tileElement);
            }
        }

        if (tileElement->GetType() == TILE_ELEMENT_TYPE_LARGE_SCENERY)
        {
            if (!(tileElement->AsLargeScenery()->GetEntry()->large_scenery.flags & LARGE_SCENERY_FLAG_PHOTOGENIC))
            {
                continue;
            }

            *rideSeatToView = 0;
            if (tileElement->clearance_height >= peep->next_z + 8)
            {
                *rideSeatToView = 0x02;
            }

            *rideToView = RIDE_ID_NULL;

            return true;
        }
    } while (!(tileElement++)->IsLastForTile());

    // TODO: Extract loop C
    tileElement = reinterpret_cast<TileElement*>(surfaceElement);
    do
    {
        // Ghosts are purely this-client-side and should not cause any interaction,
        // as that may lead to a desync.
        if (network_get_mode() != NETWORK_MODE_NONE)
        {
            if (tileElement->IsGhost())
                continue;
        }
        if (tileElement->clearance_height + 1 < peep->next_z)
            continue;
        if (peep->next_z + 6 < tileElement->base_height)
            continue;
        if (tileElement->GetType() == TILE_ELEMENT_TYPE_SURFACE)
            continue;
        if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
            continue;

        if (tileElement->GetType() == TILE_ELEMENT_TYPE_WALL)
        {
            auto wallEntry = tileElement->AsWall()->GetEntry();
            if (wallEntry == nullptr || (wallEntry->wall.flags2 & WALL_SCENERY_2_IS_OPAQUE))
            {
                continue;
            }
        }

        return false;
    } while (!(tileElement++)->IsLastForTile());

    x += CoordsDirectionDelta[edge].x;
    y += CoordsDirectionDelta[edge].y;
    if (x > 255 * 32 || y > 255 * 32)
    {
        return false;
    }

    surfaceElement = map_get_surface_element_at({ x, y });

    // TODO: extract loop A
    tileElement = reinterpret_cast<TileElement*>(surfaceElement);

    if (tileElement == nullptr)
    {
        return false;
    }

    do
    {
        // Ghosts are purely this-client-side and should not cause any interaction,
        // as that may lead to a desync.
        if (network_get_mode() != NETWORK_MODE_NONE)
        {
            if (tileElement->IsGhost())
                continue;
        }
        if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
            continue;
        if (direction_reverse(tileElement->GetDirection()) != edge)
            continue;
        auto wallEntry = tileElement->AsWall()->GetEntry();
        if (wallEntry == nullptr || (wallEntry->wall.flags2 & WALL_SCENERY_2_IS_OPAQUE))
            continue;
        if (peep->next_z + 6 <= tileElement->base_height)
            continue;
        if (peep->next_z >= tileElement->clearance_height)
            continue;

        return false;
    } while (!(tileElement++)->IsLastForTile());

    // TODO: Extract loop B
    tileElement = reinterpret_cast<TileElement*>(surfaceElement);
    do
    {
        // Ghosts are purely this-client-side and should not cause any interaction,
        // as that may lead to a desync.
        if (network_get_mode() != NETWORK_MODE_NONE)
        {
            if (tileElement->IsGhost())
                continue;
        }
        if (tileElement->clearance_height + 1 < peep->next_z)
            continue;
        if (peep->next_z + 8 < tileElement->base_height)
            continue;

        if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
        {
            if (peep_should_watch_ride(tileElement))
            {
                return loc_690FD0(peep, rideToView, rideSeatToView, tileElement);
            }
        }

        if (tileElement->GetType() == TILE_ELEMENT_TYPE_LARGE_SCENERY)
        {
            auto sceneryEntry = tileElement->AsLargeScenery()->GetEntry();
            if (!(sceneryEntry == nullptr || sceneryEntry->large_scenery.flags & LARGE_SCENERY_FLAG_PHOTOGENIC))
            {
                continue;
            }

            *rideSeatToView = 0;
            if (tileElement->clearance_height >= peep->next_z + 8)
            {
                *rideSeatToView = 0x02;
            }

            *rideToView = RIDE_ID_NULL;

            return true;
        }
    } while (!(tileElement++)->IsLastForTile());

    // TODO: Extract loop C
    tileElement = reinterpret_cast<TileElement*>(surfaceElement);
    do
    {
        // Ghosts are purely this-client-side and should not cause any interaction,
        // as that may lead to a desync.
        if (network_get_mode() != NETWORK_MODE_NONE)
        {
            if (tileElement->IsGhost())
                continue;
        }
        if (tileElement->clearance_height + 1 < peep->next_z)
            continue;
        if (peep->next_z + 8 < tileElement->base_height)
            continue;
        if (tileElement->GetType() == TILE_ELEMENT_TYPE_SURFACE)
            continue;
        if (tileElement->GetType() == TILE_ELEMENT_TYPE_PATH)
            continue;

        if (tileElement->GetType() == TILE_ELEMENT_TYPE_WALL)
        {
            auto wallEntry = tileElement->AsWall()->GetEntry();
            if (wallEntry == nullptr || (wallEntry->wall.flags2 & WALL_SCENERY_2_IS_OPAQUE))
            {
                continue;
            }
        }

        return false;
    } while (!(tileElement++)->IsLastForTile());

    x += CoordsDirectionDelta[edge].x;
    y += CoordsDirectionDelta[edge].y;
    if (x > 255 * 32 || y > 255 * 32)
    {
        return false;
    }

    surfaceElement = map_get_surface_element_at({ x, y });

    // TODO: extract loop A
    tileElement = reinterpret_cast<TileElement*>(surfaceElement);
    if (tileElement == nullptr)
    {
        return false;
    }

    do
    {
        // Ghosts are purely this-client-side and should not cause any interaction,
        // as that may lead to a desync.
        if (network_get_mode() != NETWORK_MODE_NONE)
        {
            if (tileElement->IsGhost())
                continue;
        }
        if (tileElement->GetType() != TILE_ELEMENT_TYPE_WALL)
            continue;
        if (direction_reverse(tileElement->GetDirection()) != edge)
            continue;
        auto wallEntry = tileElement->AsWall()->GetEntry();
        if (wallEntry == nullptr || (wallEntry->wall.flags2 & WALL_SCENERY_2_IS_OPAQUE))
            continue;
        if (peep->next_z + 8 <= tileElement->base_height)
            continue;
        if (peep->next_z >= tileElement->clearance_height)
            continue;

        return false;
    } while (!(tileElement++)->IsLastForTile());

    // TODO: Extract loop B
    tileElement = reinterpret_cast<TileElement*>(surfaceElement);
    do
    {
        // Ghosts are purely this-client-side and should not cause any interaction,
        // as that may lead to a desync.
        if (network_get_mode() != NETWORK_MODE_NONE)
        {
            if (tileElement->IsGhost())
                continue;
        }
        if (tileElement->clearance_height + 1 < peep->next_z)
            continue;
        if (peep->next_z + 10 < tileElement->base_height)
            continue;

        if (tileElement->GetType() == TILE_ELEMENT_TYPE_TRACK)
        {
            if (peep_should_watch_ride(tileElement))
            {
                return loc_690FD0(peep, rideToView, rideSeatToView, tileElement);
            }
        }

        if (tileElement->GetType() == TILE_ELEMENT_TYPE_LARGE_SCENERY)
        {
            if (!(tileElement->AsLargeScenery()->GetEntry()->large_scenery.flags & LARGE_SCENERY_FLAG_PHOTOGENIC))
            {
                continue;
            }

            *rideSeatToView = 0;
            if (tileElement->clearance_height >= peep->next_z + 8)
            {
                *rideSeatToView = 0x02;
            }

            *rideToView = RIDE_ID_NULL;

            return true;
        }
    } while (!(tileElement++)->IsLastForTile());

    return false;
}

/* Part of 0x0069B8CC rct2: 0x0069BC31 */
void Guest::SetSpriteType(PeepSpriteType new_sprite_type)
{
    if (sprite_type == new_sprite_type)
        return;

    sprite_type = new_sprite_type;
    action_sprite_image_offset = 0;
    no_action_frame_num = 0;

    if (action >= PEEP_ACTION_NONE_1)
        action = PEEP_ACTION_NONE_2;

    peep_flags &= ~PEEP_FLAGS_SLOW_WALK;
    Guard::Assert(new_sprite_type < std::size(gSpriteTypeToSlowWalkMap));
    if (gSpriteTypeToSlowWalkMap[new_sprite_type])
    {
        peep_flags |= PEEP_FLAGS_SLOW_WALK;
    }

    action_sprite_type = PEEP_ACTION_SPRITE_TYPE_INVALID;
    UpdateCurrentActionSpriteType();

    if (state == PEEP_STATE_SITTING)
    {
        action = PEEP_ACTION_NONE_1;
        next_action_sprite_type = PEEP_ACTION_SPRITE_TYPE_SITTING_IDLE;
        SwitchNextActionSpriteType();
    }
    if (state == PEEP_STATE_WATCHING)
    {
        action = PEEP_ACTION_NONE_1;
        next_action_sprite_type = PEEP_ACTION_SPRITE_TYPE_WATCH_RIDE;
        SwitchNextActionSpriteType();
    }
}

struct item_pref_t
{
    uint8_t type;  // 0 for standard, 1 for extra
    uint32_t item; // And this with the relevant flags
    PeepSpriteType sprite_type;
};

// clang-format off
static item_pref_t item_order_preference[] = {
        { 0, PEEP_ITEM_ICE_CREAM, PEEP_SPRITE_TYPE_ICE_CREAM },
        { 0, PEEP_ITEM_CHIPS, PEEP_SPRITE_TYPE_CHIPS },
        { 0, PEEP_ITEM_PIZZA, PEEP_SPRITE_TYPE_PIZZA },
        { 0, PEEP_ITEM_BURGER, PEEP_SPRITE_TYPE_BURGER },
        { 0, PEEP_ITEM_DRINK, PEEP_SPRITE_TYPE_DRINK },
        { 0, PEEP_ITEM_COFFEE, PEEP_SPRITE_TYPE_COFFEE },
        { 0, PEEP_ITEM_CHICKEN, PEEP_SPRITE_TYPE_CHICKEN },
        { 0, PEEP_ITEM_LEMONADE, PEEP_SPRITE_TYPE_LEMONADE },
        { 0, PEEP_ITEM_CANDYFLOSS, PEEP_SPRITE_TYPE_CANDYFLOSS },
        { 0, PEEP_ITEM_POPCORN, PEEP_SPRITE_TYPE_POPCORN },
        { 0, PEEP_ITEM_HOT_DOG, PEEP_SPRITE_TYPE_HOT_DOG  },
        { 0, PEEP_ITEM_TENTACLE, PEEP_SPRITE_TYPE_TENTACLE },
        { 0, PEEP_ITEM_TOFFEE_APPLE, PEEP_SPRITE_TYPE_TOFFEE_APPLE },
        { 0, PEEP_ITEM_DOUGHNUT, PEEP_SPRITE_TYPE_DOUGHNUT },
        { 1, PEEP_ITEM_PRETZEL, PEEP_SPRITE_TYPE_PRETZEL },
        { 1, PEEP_ITEM_COOKIE, PEEP_SPRITE_TYPE_PRETZEL },
        { 1, PEEP_ITEM_CHOCOLATE, PEEP_SPRITE_TYPE_COFFEE },
        { 1, PEEP_ITEM_ICED_TEA, PEEP_SPRITE_TYPE_COFFEE },
        { 1, PEEP_ITEM_FUNNEL_CAKE, PEEP_SPRITE_TYPE_FUNNEL_CAKE },
        { 1, PEEP_ITEM_BEEF_NOODLES, PEEP_SPRITE_TYPE_NOODLES },
        { 1, PEEP_ITEM_FRIED_RICE_NOODLES, PEEP_SPRITE_TYPE_NOODLES },
        { 1, PEEP_ITEM_WONTON_SOUP, PEEP_SPRITE_TYPE_SOUP },
        { 1, PEEP_ITEM_MEATBALL_SOUP, PEEP_SPRITE_TYPE_SOUP },
        { 1, PEEP_ITEM_FRUIT_JUICE, PEEP_SPRITE_TYPE_JUICE },
        { 1, PEEP_ITEM_SOYBEAN_MILK, PEEP_SPRITE_TYPE_SU_JONGKWA },
        { 1, PEEP_ITEM_SU_JONGKWA, PEEP_SPRITE_TYPE_SU_JONGKWA },
        { 1, PEEP_ITEM_SUB_SANDWICH, PEEP_SPRITE_TYPE_SANDWICH },
        { 1, PEEP_ITEM_ROAST_SAUSAGE, PEEP_SPRITE_TYPE_SAUSAGE },
        { 0, PEEP_ITEM_BALLOON, PEEP_SPRITE_TYPE_BALLOON },
        { 0, PEEP_ITEM_HAT, PEEP_SPRITE_TYPE_HAT },
        { 1, PEEP_ITEM_SUNGLASSES, PEEP_SPRITE_TYPE_SUNGLASSES },
        { 0xFF, 0xFFFFFFFF, PEEP_SPRITE_TYPE_INVALID }
};
// clang-format on

/**
 *
 *  rct2: 0x0069B8CC
 */
void Guest::UpdateSpriteType()
{
    if (sprite_type == PEEP_SPRITE_TYPE_BALLOON && (scenario_rand() & 0xFFFF) <= 327)
    {
        bool isBalloonPopped = false;
        if (x != LOCATION_NULL)
        {
            if ((scenario_rand() & 0xFFFF) <= 13107)
            {
                isBalloonPopped = true;
                audio_play_sound_at_location(SoundId::BalloonPop, { x, y, z });
            }
            create_balloon(x, y, z + 9, balloon_colour, isBalloonPopped);
        }
        item_standard_flags &= ~PEEP_ITEM_BALLOON;
        window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY;
    }

    if (climate_is_raining() && (item_standard_flags & PEEP_ITEM_UMBRELLA) && x != LOCATION_NULL)
    {
        if ((x & 0xFFE0) < 0x1FFF && (y & 0xFFE0) < 0x1FFF)
        {
            TileElement* tileElement = map_get_first_element_at(x / 32, y / 32);
            while (true)
            {
                if (tileElement == nullptr)
                    break;
                if ((z / 8) < tileElement->base_height)
                    break;

                if (tileElement->IsLastForTile())
                {
                    SetSpriteType(PEEP_SPRITE_TYPE_UMBRELLA);
                    return;
                }
                tileElement++;
            }
        }
    }

    for (item_pref_t* item_pref = item_order_preference; item_pref->type != 0xFF; item_pref++)
    {
        if (item_pref->type == 0)
        {
            if (item_standard_flags & item_pref->item)
            {
                SetSpriteType(item_pref->sprite_type);
                return;
            }
        }
        else
        {
            if (item_extra_flags & item_pref->item)
            {
                SetSpriteType(item_pref->sprite_type);
                return;
            }
        }
    }

    if (state == PEEP_STATE_WATCHING && standing_flags & (1 << 1))
    {
        SetSpriteType(PEEP_SPRITE_TYPE_WATCHING);
        return;
    }

    if (nausea > 170)
    {
        SetSpriteType(PEEP_SPRITE_TYPE_VERY_NAUSEOUS);
        return;
    }

    if (nausea > 140)
    {
        SetSpriteType(PEEP_SPRITE_TYPE_NAUSEOUS);
        return;
    }

    if (energy <= 64 && happiness < 128)
    {
        SetSpriteType(PEEP_SPRITE_TYPE_HEAD_DOWN);
        return;
    }

    if (energy <= 80 && happiness < 128)
    {
        SetSpriteType(PEEP_SPRITE_TYPE_ARMS_CROSSED);
        return;
    }

    if (toilet > 220)
    {
        SetSpriteType(PEEP_SPRITE_TYPE_REQUIRE_BATHROOM);
        return;
    }

    SetSpriteType(PEEP_SPRITE_TYPE_NORMAL);
}

bool Guest::HeadingForRideOrParkExit() const
{
    return (peep_flags & PEEP_FLAGS_LEAVING_PARK) || (guest_heading_to_ride_id != 0xFF);
}
