mirror of https://github.com/OpenRCT2/OpenRCT2.git
6795 lines
202 KiB
C++
6795 lines
202 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2019 OpenRCT2 developers
|
|
*
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
|
*****************************************************************************/
|
|
|
|
#include "../Context.h"
|
|
#include "../Game.h"
|
|
#include "../OpenRCT2.h"
|
|
#include "../audio/audio.h"
|
|
#include "../config/Config.h"
|
|
#include "../core/Guard.hpp"
|
|
#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 LocationXY16 _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
|
|
};
|
|
|
|
// 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);
|
|
|
|
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)
|
|
{
|
|
peep_insert_new_thought(this, 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(SOUND_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)
|
|
{
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
PeepThoughtType thought_type = ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_IN_RIDE)
|
|
? PEEP_THOUGHT_TYPE_GET_OUT
|
|
: PEEP_THOUGHT_TYPE_GET_OFF;
|
|
|
|
peep_insert_new_thought(this, 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];
|
|
|
|
peep_insert_new_thought(this, 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);
|
|
}
|
|
peep_insert_new_thought(this, 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->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();
|
|
invalidate_sprite_2((rct_sprite*)this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
};
|
|
|
|
Invalidate();
|
|
MoveTo(loc.x, loc.y, loc.z);
|
|
|
|
sprite_direction = ((var_37 + 2) & 3) * 8;
|
|
Invalidate();
|
|
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();
|
|
Invalidate();
|
|
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();
|
|
Invalidate();
|
|
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 (gRideCount < 2)
|
|
return;
|
|
peep_flags ^= PEEP_FLAGS_21;
|
|
|
|
if (!(peep_flags & PEEP_FLAGS_21))
|
|
return;
|
|
|
|
time_lost++;
|
|
if (time_lost != 254)
|
|
return;
|
|
time_lost = 230;
|
|
}
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
peep_insert_new_thought(this, 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))
|
|
{
|
|
peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_ALREADY_GOT, shopItem);
|
|
return false;
|
|
}
|
|
|
|
if (shop_item_is_food_or_drink(shopItem))
|
|
{
|
|
int32_t food = -1;
|
|
if ((food = HasFoodStandardFlag()) != 0)
|
|
{
|
|
peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_HAVENT_FINISHED, bitscanforward(food));
|
|
return false;
|
|
}
|
|
else if ((food = HasFoodExtraFlag()) != 0)
|
|
{
|
|
peep_insert_new_thought(this, 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))
|
|
{
|
|
peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_NOT_HUNGRY, PEEP_THOUGHT_ITEM_NONE);
|
|
return false;
|
|
}
|
|
|
|
if (shop_item_is_drink(shopItem) && (thirst > 75))
|
|
{
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
if (cash_in_pocket == 0)
|
|
{
|
|
peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_SPENT_MONEY, PEEP_THOUGHT_ITEM_NONE);
|
|
return false;
|
|
}
|
|
if (price > cash_in_pocket)
|
|
{
|
|
peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_CANT_AFFORD, shopItem);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (gClimateCurrent.Temperature >= 21)
|
|
itemValue = get_shop_hot_value(shopItem);
|
|
else if (gClimateCurrent.Temperature <= 11)
|
|
itemValue = get_shop_cold_value(shopItem);
|
|
else
|
|
itemValue = get_shop_base_value(shopItem);
|
|
|
|
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)));
|
|
peep_insert_new_thought(this, 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)));
|
|
peep_insert_new_thought(this, 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 = get_shop_hot_value(shopItem);
|
|
else if (gClimateCurrent.Temperature <= 11)
|
|
itemValue = get_shop_cold_value(shopItem);
|
|
else
|
|
itemValue = get_shop_base_value(shopItem);
|
|
|
|
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)
|
|
{
|
|
set_format_arg(0, rct_string_id, name_string_idx);
|
|
set_format_arg(2, uint32_t, id);
|
|
set_format_arg(6, rct_string_id, ShopItemStringIds[shopItem].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(get_shop_item_cost(shopItem), 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 - get_shop_item_cost(shopItem));
|
|
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)
|
|
{
|
|
Ride* ride = get_ride(rideIndex);
|
|
|
|
// 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)
|
|
{
|
|
Ride* 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 (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 (peep_really_liked_ride(this, ride))
|
|
{
|
|
peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_WAS_GREAT, rideIndex);
|
|
|
|
int32_t laugh = scenario_rand() & 7;
|
|
if (laugh < 3)
|
|
{
|
|
audio_play_sound_at_location(SOUND_LAUGH_1 + laugh, x, y, z);
|
|
}
|
|
}
|
|
|
|
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 (int32_t i = 0; i < MAX_RIDES; i++)
|
|
{
|
|
if (rideConsideration[i])
|
|
{
|
|
auto ride = get_ride(i);
|
|
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
|
|
int32_t i;
|
|
Ride* ride;
|
|
FOR_ALL_RIDES (i, ride)
|
|
{
|
|
if (!HasRidden(ride))
|
|
{
|
|
rideConsideration[i] = 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)
|
|
int32_t i;
|
|
Ride* ride;
|
|
FOR_ALL_RIDES (i, ride)
|
|
{
|
|
if (ride->highest_drop_height > 66 || ride->excitement >= RIDE_RATING(8, 00))
|
|
{
|
|
rideConsideration[i] = 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))
|
|
{
|
|
if (ridePrice > cash_in_pocket)
|
|
{
|
|
if (peepAtRide)
|
|
{
|
|
if (cash_in_pocket <= 0)
|
|
{
|
|
peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_SPENT_MONEY, PEEP_THOUGHT_ITEM_NONE);
|
|
}
|
|
else
|
|
{
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
peep_insert_new_thought(this, 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))
|
|
{
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
peep_insert_new_thought(this, 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)
|
|
{
|
|
peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_SPENT_MONEY, PEEP_THOUGHT_ITEM_NONE);
|
|
}
|
|
else
|
|
{
|
|
peep_insert_new_thought(this, 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);
|
|
|
|
gUnk141F568 = gUnk13CA740;
|
|
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)
|
|
{
|
|
money_effect_create_at(amount, x, y, z, true);
|
|
}
|
|
}
|
|
|
|
audio_play_sound_at_location(SOUND_PURCHASE, x, y, z);
|
|
}
|
|
|
|
void Guest::SetHasRidden(Ride* ride)
|
|
{
|
|
rides_been_on[ride->id / 8] |= 1 << (ride->id % 8);
|
|
SetHasRiddenRideType(ride->type);
|
|
}
|
|
|
|
bool Guest::HasRidden(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();
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
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_insert_new_thought(peep, 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)
|
|
{
|
|
peep_insert_new_thought(peep, 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_insert_new_thought(peep, 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_insert_new_thought(peep, 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) & 0xFFFF) > 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);
|
|
|
|
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->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_insert_new_thought(peep, 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);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x00695B70
|
|
*/
|
|
static void peep_head_for_nearest_ride_type(Guest* peep, int32_t rideType)
|
|
{
|
|
Ride* ride;
|
|
|
|
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)
|
|
{
|
|
ride = get_ride(peep->guest_heading_to_ride_id);
|
|
if (ride->type == rideType)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
uint32_t rideConsideration[8]{};
|
|
|
|
// FIX Originally checked for a toy,.likely a mistake and should be a map
|
|
if ((peep->item_standard_flags & PEEP_ITEM_MAP) && rideType != RIDE_TYPE_FIRST_AID)
|
|
{
|
|
// Consider all rides in the park
|
|
int32_t i;
|
|
FOR_ALL_RIDES (i, ride)
|
|
{
|
|
if (ride->type == rideType)
|
|
{
|
|
rideConsideration[i >> 5] |= (1u << (i & 0x1F));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Take nearby rides into consideration
|
|
int32_t cx = floor2(peep->x, 32);
|
|
int32_t cy = floor2(peep->y, 32);
|
|
for (int32_t x = cx - 320; x <= cx + 320; x += 32)
|
|
{
|
|
for (int32_t y = cy - 320; y <= cy + 320; y += 32)
|
|
{
|
|
if (x >= 0 && y >= 0 && x < (256 * 32) && y < (256 * 32))
|
|
{
|
|
TileElement* tileElement = map_get_first_element_at(x >> 5, y >> 5);
|
|
do
|
|
{
|
|
if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
|
|
continue;
|
|
|
|
ride_id_t rideIndex = tileElement->AsTrack()->GetRideIndex();
|
|
ride = get_ride(rideIndex);
|
|
if (ride->type == rideType)
|
|
{
|
|
rideConsideration[rideIndex >> 5] |= (1u << (rideIndex & 0x1F));
|
|
}
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Filter the considered rides
|
|
uint8_t potentialRides[256];
|
|
uint8_t* nextPotentialRide = &potentialRides[0];
|
|
int32_t numPotentialRides = 0;
|
|
for (int32_t i = 0; i < MAX_RIDES; i++)
|
|
{
|
|
if (!(rideConsideration[i >> 5] & (1u << (i & 0x1F))))
|
|
continue;
|
|
|
|
ride = get_ride(i);
|
|
if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_QUEUE_FULL))
|
|
{
|
|
if (peep->ShouldGoOnRide(ride, 0, false, true))
|
|
{
|
|
*nextPotentialRide++ = i;
|
|
numPotentialRides++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pick the closest ride
|
|
ride_id_t closestRideIndex = RIDE_ID_NULL;
|
|
int32_t closestRideDistance = std::numeric_limits<int32_t>::max();
|
|
for (int32_t i = 0; i < numPotentialRides; i++)
|
|
{
|
|
ride = get_ride(potentialRides[i]);
|
|
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)
|
|
{
|
|
closestRideIndex = potentialRides[i];
|
|
closestRideDistance = distance;
|
|
}
|
|
}
|
|
if (closestRideIndex == RIDE_ID_NULL)
|
|
return;
|
|
|
|
// Head to that ride
|
|
peep->guest_heading_to_ride_id = closestRideIndex;
|
|
peep->peep_is_lost_countdown = 200;
|
|
peep_reset_pathfind_goal(peep);
|
|
peep->window_invalidate_flags |= PEEP_INVALIDATE_PEEP_ACTION;
|
|
|
|
peep->time_lost = 0;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006958D0
|
|
*/
|
|
static void peep_head_for_nearest_ride_with_flags(Guest* peep, int32_t rideTypeFlags)
|
|
{
|
|
Ride* ride;
|
|
|
|
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)
|
|
{
|
|
ride = get_ride(peep->guest_heading_to_ride_id);
|
|
if (ride_type_has_flag(
|
|
ride->type, RIDE_TYPE_FLAG_IS_BATHROOM | RIDE_TYPE_FLAG_SELLS_DRINKS | RIDE_TYPE_FLAG_SELLS_FOOD))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((rideTypeFlags & RIDE_TYPE_FLAG_IS_BATHROOM) && peep->HasFood())
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint32_t rideConsideration[8]{};
|
|
|
|
// FIX Originally checked for a toy,.likely a mistake and should be a map
|
|
if (peep->item_standard_flags & PEEP_ITEM_MAP)
|
|
{
|
|
// Consider all rides in the park
|
|
int32_t i;
|
|
FOR_ALL_RIDES (i, ride)
|
|
{
|
|
if (ride_type_has_flag(ride->type, rideTypeFlags))
|
|
{
|
|
rideConsideration[i >> 5] |= (1u << (i & 0x1F));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Take nearby rides into consideration
|
|
int32_t cx = floor2(peep->x, 32);
|
|
int32_t cy = floor2(peep->y, 32);
|
|
for (int32_t x = cx - 320; x <= cx + 320; x += 32)
|
|
{
|
|
for (int32_t y = cy - 320; y <= cy + 320; y += 32)
|
|
{
|
|
if (x >= 0 && y >= 0 && x < (256 * 32) && y < (256 * 32))
|
|
{
|
|
TileElement* tileElement = map_get_first_element_at(x >> 5, y >> 5);
|
|
do
|
|
{
|
|
if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
|
|
continue;
|
|
|
|
ride_id_t rideIndex = tileElement->AsTrack()->GetRideIndex();
|
|
ride = get_ride(rideIndex);
|
|
if (ride_type_has_flag(ride->type, rideTypeFlags))
|
|
{
|
|
rideConsideration[rideIndex >> 5] |= (1u << (rideIndex & 0x1F));
|
|
}
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Filter the considered rides
|
|
uint8_t potentialRides[256];
|
|
uint8_t* nextPotentialRide = &potentialRides[0];
|
|
int32_t numPotentialRides = 0;
|
|
for (int32_t i = 0; i < MAX_RIDES; i++)
|
|
{
|
|
if (!(rideConsideration[i >> 5] & (1u << (i & 0x1F))))
|
|
continue;
|
|
|
|
ride = get_ride(i);
|
|
if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_QUEUE_FULL))
|
|
{
|
|
if (peep->ShouldGoOnRide(ride, 0, false, true))
|
|
{
|
|
*nextPotentialRide++ = i;
|
|
numPotentialRides++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pick the closest ride
|
|
ride_id_t closestRideIndex = RIDE_ID_NULL;
|
|
int32_t closestRideDistance = std::numeric_limits<int32_t>::max();
|
|
for (int32_t i = 0; i < numPotentialRides; i++)
|
|
{
|
|
ride = get_ride(potentialRides[i]);
|
|
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)
|
|
{
|
|
closestRideIndex = potentialRides[i];
|
|
closestRideDistance = distance;
|
|
}
|
|
}
|
|
if (closestRideIndex == RIDE_ID_NULL)
|
|
return;
|
|
|
|
// Head to that ride
|
|
peep->guest_heading_to_ride_id = closestRideIndex;
|
|
peep->peep_is_lost_countdown = 200;
|
|
peep_reset_pathfind_goal(peep);
|
|
|
|
// Invalidate windows
|
|
rct_window* w = window_find_by_number(WC_PEEP, peep->sprite_index);
|
|
if (w != nullptr)
|
|
{
|
|
window_event_invalidate_call(w);
|
|
window_invalidate(w);
|
|
}
|
|
|
|
peep->time_lost = 0;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* 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;
|
|
|
|
Ride* ride = get_ride(rideIndex);
|
|
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;
|
|
|
|
Ride* ride = get_ride(current_ride);
|
|
if (ride->type == RIDE_TYPE_NULL || ride->status != RIDE_STATUS_OPEN)
|
|
{
|
|
SetState(PEEP_STATE_FALLING);
|
|
return;
|
|
}
|
|
|
|
if (sub_state == 1)
|
|
{
|
|
if (action != PEEP_ACTION_NONE_2)
|
|
{
|
|
int16_t actionX;
|
|
int16_t actionY;
|
|
int16_t xy_distance;
|
|
UpdateAction(&actionX, &actionY, &xy_distance);
|
|
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();
|
|
Invalidate();
|
|
|
|
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()
|
|
{
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
// 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)
|
|
{
|
|
Invalidate();
|
|
|
|
int16_t actionX, actionY, xy_distance;
|
|
|
|
if (UpdateAction(&actionX, &actionY, &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(actionX, actionY, actionZ);
|
|
Invalidate();
|
|
}
|
|
else
|
|
{
|
|
destination_tolerance = 0;
|
|
sprite_direction ^= (1 << 4);
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
int16_t actionX, actionY, actionZ, xy_distance;
|
|
|
|
Ride* ride = get_ride(current_ride);
|
|
rct_ride_entry* ride_entry = get_ride_entry(ride->subtype);
|
|
|
|
if (UpdateAction(&actionX, &actionY, &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;
|
|
}
|
|
|
|
Invalidate();
|
|
|
|
actionZ = ride->stations[current_ride_station].Height * 8;
|
|
|
|
distanceThreshold += 4;
|
|
if (xy_distance < distanceThreshold)
|
|
{
|
|
actionZ += RideData5[ride->type].z;
|
|
}
|
|
|
|
MoveTo(actionX, actionY, actionZ);
|
|
Invalidate();
|
|
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;
|
|
|
|
set_format_arg(0, rct_string_id, ride->name);
|
|
set_format_arg(2, uint32_t, ride->name_arguments);
|
|
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;
|
|
|
|
sprite_move(x, y, z, (rct_sprite*)peep);
|
|
peep->Invalidate();
|
|
|
|
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)
|
|
{
|
|
set_format_arg(0, rct_string_id, name_string_idx);
|
|
set_format_arg(2, uint32_t, id);
|
|
set_format_arg(6, rct_string_id, ride->name);
|
|
set_format_arg(8, uint32_t, ride->name_arguments);
|
|
|
|
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()
|
|
{
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
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()
|
|
{
|
|
int16_t actionX, actionY, xy_distance;
|
|
if (!UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
sub_state = PEEP_RIDE_ENTER_VEHICLE;
|
|
return;
|
|
}
|
|
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
}
|
|
|
|
void Guest::UpdateRideEnterVehicle()
|
|
{
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
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);
|
|
}
|
|
|
|
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 seated_peep = (GET_PEEP(vehicle->peep[current_seat ^ 1]))->AsGuest();
|
|
if (seated_peep == nullptr || seated_peep->sub_state != PEEP_RIDE_ENTER_VEHICLE)
|
|
return;
|
|
|
|
vehicle->num_peeps++;
|
|
ride->cur_num_customers++;
|
|
|
|
vehicle->mass += seated_peep->mass;
|
|
seated_peep->Invalidate();
|
|
sprite_move(LOCATION_NULL, 0, 0, (rct_sprite*)seated_peep);
|
|
|
|
seated_peep->SetState(PEEP_STATE_ON_RIDE);
|
|
seated_peep->time_on_ride = 0;
|
|
seated_peep->sub_state = PEEP_RIDE_ON_RIDE;
|
|
seated_peep->OnEnterRide(current_ride);
|
|
}
|
|
|
|
vehicle->num_peeps++;
|
|
ride->cur_num_customers++;
|
|
|
|
vehicle->mass += mass;
|
|
invalidate_sprite_2((rct_sprite*)vehicle);
|
|
|
|
Invalidate();
|
|
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()
|
|
{
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
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);
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
Invalidate();
|
|
|
|
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)
|
|
{
|
|
Ride* ride = get_ride(peep->current_ride);
|
|
|
|
Guard::Assert(peep->current_ride_station < std::size(ride->stations), GUARD_LINE);
|
|
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()
|
|
{
|
|
int16_t actionX, actionY, xy_distance;
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
return;
|
|
}
|
|
|
|
peep_update_ride_prepare_for_exit(this);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0069382E
|
|
*/
|
|
void Guest::UpdateRideInExit()
|
|
{
|
|
int16_t actionX, actionY, xy_distance;
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
|
|
if (xy_distance >= 16)
|
|
{
|
|
int16_t actionZ = ride->stations[current_ride_station].Height * 8;
|
|
|
|
actionZ += RideData5[ride->type].z;
|
|
MoveTo(actionX, actionY, actionZ);
|
|
Invalidate();
|
|
return;
|
|
}
|
|
|
|
SwitchToSpecialSprite(0);
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006926AD
|
|
*/
|
|
void Guest::UpdateRideApproachVehicleWaypoints()
|
|
{
|
|
int16_t actionX, actionY, xy_distance;
|
|
Ride* ride = get_ride(current_ride);
|
|
uint8_t waypoint = var_37 & 3;
|
|
|
|
if (UpdateAction(&actionX, &actionY, &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;
|
|
}
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, actionZ);
|
|
Invalidate();
|
|
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]);
|
|
|
|
actionX = ride->stations[current_ride_station].Start.x * 32 + 16;
|
|
actionY = ride->stations[current_ride_station].Start.y * 32 + 16;
|
|
|
|
if (ride->type == RIDE_TYPE_ENTERPRISE)
|
|
{
|
|
actionX = vehicle->x;
|
|
actionY = 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);
|
|
actionX += vehicle_type->peep_loading_waypoints[var_37 / 4][waypoint].x;
|
|
actionY += vehicle_type->peep_loading_waypoints[var_37 / 4][waypoint].y;
|
|
|
|
destination_x = actionX;
|
|
destination_y = actionY;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0069357D
|
|
*/
|
|
void Guest::UpdateRideApproachExitWaypoints()
|
|
{
|
|
int16_t actionX, actionY, xy_distance;
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
if (UpdateAction(&actionX, &actionY, &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;
|
|
}
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, actionZ);
|
|
Invalidate();
|
|
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]);
|
|
|
|
actionX = ride->stations[current_ride_station].Start.x * 32 + 16;
|
|
actionY = ride->stations[current_ride_station].Start.y * 32 + 16;
|
|
|
|
if (ride->type == RIDE_TYPE_ENTERPRISE)
|
|
{
|
|
actionX = vehicle->x;
|
|
actionY = 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);
|
|
actionX += vehicleEntry->peep_loading_waypoints[var_37 / 4][var_37 & 3].x;
|
|
actionY += vehicleEntry->peep_loading_waypoints[var_37 / 4][var_37 & 3].y;
|
|
|
|
destination_x = actionX;
|
|
destination_y = actionY;
|
|
return;
|
|
}
|
|
|
|
var_37 |= 3;
|
|
|
|
auto exit = ride_get_exit_location(ride, current_ride_station);
|
|
actionX = exit.x;
|
|
actionY = exit.y;
|
|
uint8_t exit_direction = direction_reverse(exit.direction);
|
|
|
|
actionX *= 32;
|
|
actionY *= 32;
|
|
actionX += 16;
|
|
actionY += 16;
|
|
|
|
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;
|
|
|
|
actionX -= x_shift;
|
|
actionY -= y_shift;
|
|
|
|
destination_x = actionX;
|
|
destination_y = actionY;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006927B3
|
|
*/
|
|
void Guest::UpdateRideApproachSpiralSlide()
|
|
{
|
|
int16_t actionX, actionY, xy_distance;
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
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, actionY, 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;
|
|
actionX = ride->stations[current_ride_station].Start.x * 32;
|
|
actionY = ride->stations[current_ride_station].Start.y * 32;
|
|
|
|
assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
|
|
const CoordsXY slidePlatformDestination = SpiralSlideWalkingPath[var_37];
|
|
|
|
actionX += slidePlatformDestination.x;
|
|
actionY += slidePlatformDestination.y;
|
|
|
|
destination_x = actionX;
|
|
destination_y = actionY;
|
|
sub_state = PEEP_RIDE_LEAVE_SPIRAL_SLIDE;
|
|
return;
|
|
}
|
|
}
|
|
waypoint++;
|
|
// Actually increment the real peep waypoint
|
|
var_37++;
|
|
|
|
actionX = ride->stations[current_ride_station].Start.x * 32;
|
|
actionY = ride->stations[current_ride_station].Start.y * 32;
|
|
|
|
assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
|
|
const CoordsXY slidePlatformDestination = SpiralSlideWalkingPath[var_37];
|
|
|
|
actionX += slidePlatformDestination.x;
|
|
actionY += slidePlatformDestination.y;
|
|
|
|
destination_x = actionX;
|
|
destination_y = actionY;
|
|
}
|
|
|
|
/** 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()
|
|
{
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
if (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;
|
|
Invalidate();
|
|
|
|
var_37++;
|
|
return;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
int16_t actionX, actionY, xy_distance;
|
|
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
return;
|
|
}
|
|
uint8_t waypoint = 2;
|
|
var_37 = (var_37 * 4 & 0x30) + waypoint;
|
|
|
|
actionX = ride->stations[current_ride_station].Start.x * 32;
|
|
actionY = ride->stations[current_ride_station].Start.y * 32;
|
|
|
|
assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
|
|
const CoordsXY slidePlatformDestination = SpiralSlideWalkingPath[var_37];
|
|
|
|
actionX += slidePlatformDestination.x;
|
|
actionY += slidePlatformDestination.y;
|
|
|
|
destination_x = actionX;
|
|
destination_y = actionY;
|
|
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.
|
|
int16_t actionX, actionY, xy_distance;
|
|
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
return;
|
|
}
|
|
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
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--;
|
|
actionX = ride->stations[current_ride_station].Start.x * 32;
|
|
actionY = ride->stations[current_ride_station].Start.y * 32;
|
|
|
|
assert(ride->type == RIDE_TYPE_SPIRAL_SLIDE);
|
|
const CoordsXY slidePlatformDestination = SpiralSlideWalkingPath[var_37];
|
|
|
|
actionX += slidePlatformDestination.x;
|
|
actionY += slidePlatformDestination.y;
|
|
|
|
destination_x = actionX;
|
|
destination_y = actionY;
|
|
return;
|
|
}
|
|
waypoint = 3;
|
|
// Actually force the final waypoint
|
|
var_37 |= 3;
|
|
|
|
auto exit = ride_get_exit_location(ride, current_ride_station);
|
|
actionX = exit.x * 32 + 16;
|
|
actionY = exit.y * 32 + 16;
|
|
|
|
exit.direction = direction_reverse(exit.direction);
|
|
|
|
int16_t xShift = word_981D6C[exit.direction].x;
|
|
int16_t yShift = word_981D6C[exit.direction].y;
|
|
|
|
int16_t shiftMultiplier = 20;
|
|
|
|
xShift *= shiftMultiplier;
|
|
yShift *= shiftMultiplier;
|
|
|
|
actionX -= xShift;
|
|
actionY -= yShift;
|
|
|
|
destination_x = actionX;
|
|
destination_y = actionY;
|
|
}
|
|
|
|
/** 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()
|
|
{
|
|
int16_t actionX, actionY, xy_distance;
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
return;
|
|
}
|
|
|
|
Ride* ride = get_ride(current_ride);
|
|
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();
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
actionX = destination_x & 0xFFE0;
|
|
actionY = destination_y & 0xFFE0;
|
|
int16_t stationHeight = ride->stations[0].Height;
|
|
|
|
// Find the station track element
|
|
auto trackElement = map_get_track_element_at(actionX, actionY, 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;
|
|
}
|
|
|
|
actionX = CoordsDirectionDelta[chosenEdge].x / 2;
|
|
actionY = CoordsDirectionDelta[chosenEdge].y / 2;
|
|
|
|
actionX += destination_x;
|
|
actionY += destination_y;
|
|
|
|
enum class maze_type
|
|
{
|
|
invalid,
|
|
hedge,
|
|
entrance_or_exit
|
|
};
|
|
maze_type mazeType = maze_type::invalid;
|
|
|
|
auto tileElement = map_get_first_element_at(actionX / 32, actionY / 32);
|
|
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 = actionX;
|
|
destination_y = actionY;
|
|
|
|
var_37 = _MazeGetNewDirectionFromEdge[var_37 / 4][chosenEdge];
|
|
maze_last_edge = chosenEdge;
|
|
break;
|
|
case maze_type::entrance_or_exit:
|
|
actionX = destination_x;
|
|
actionY = destination_y;
|
|
if (chosenEdge & 1)
|
|
{
|
|
actionX &= 0xFFE0;
|
|
actionX += 16;
|
|
}
|
|
else
|
|
{
|
|
actionY &= 0xFFE0;
|
|
actionY += 16;
|
|
}
|
|
destination_x = actionX;
|
|
destination_y = actionY;
|
|
var_37 = 16;
|
|
maze_last_edge = chosenEdge;
|
|
break;
|
|
}
|
|
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006938D2
|
|
*/
|
|
void Guest::UpdateRideLeaveExit()
|
|
{
|
|
int16_t actionX, actionY, xy_distance;
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, ride->stations[current_ride_station].Height * 8);
|
|
Invalidate();
|
|
return;
|
|
}
|
|
|
|
OnExitRide(current_ride);
|
|
|
|
if (peep_flags & PEEP_FLAGS_TRACKING)
|
|
{
|
|
set_format_arg(0, rct_string_id, name_string_idx);
|
|
set_format_arg(2, uint32_t, id);
|
|
set_format_arg(6, rct_string_id, ride->name);
|
|
set_format_arg(8, uint32_t, ride->name_arguments);
|
|
|
|
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);
|
|
|
|
actionX = x & 0xFFE0;
|
|
actionY = y & 0xFFE0;
|
|
|
|
// Find the station track element
|
|
TileElement* tileElement = map_get_first_element_at(actionX / 32, actionY / 32);
|
|
do
|
|
{
|
|
if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
|
|
continue;
|
|
|
|
int16_t height = map_height_from_slope(
|
|
{ x, y }, 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);
|
|
Invalidate();
|
|
return;
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x0069299C
|
|
*/
|
|
void Guest::UpdateRideShopApproach()
|
|
{
|
|
int16_t actionX, actionY, xy_distance;
|
|
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
return;
|
|
}
|
|
|
|
sub_state = PEEP_SHOP_INTERACT;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006929BB
|
|
*/
|
|
void Guest::UpdateRideShopInteract()
|
|
{
|
|
const int16_t tileCenterX = next_x + 16;
|
|
const int16_t tileCenterY = next_y + 16;
|
|
Ride* ride = get_ride(current_ride);
|
|
|
|
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(SOUND_TOILET_FLUSH, 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()
|
|
{
|
|
int16_t actionX, actionY, xy_distance;
|
|
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
|
|
actionX = x & 0xFFE0;
|
|
actionY = y & 0xFFE0;
|
|
if (actionX != next_x)
|
|
return;
|
|
if (actionY != next_y)
|
|
return;
|
|
}
|
|
|
|
SetState(PEEP_STATE_WALKING);
|
|
|
|
Ride* ride = get_ride(current_ride);
|
|
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)
|
|
{
|
|
Invalidate();
|
|
|
|
action = PEEP_ACTION_WAVE_2;
|
|
action_frame = 0;
|
|
action_sprite_image_offset = 0;
|
|
|
|
UpdateCurrentActionSpriteType();
|
|
Invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (peep_flags & PEEP_FLAGS_PHOTO)
|
|
{
|
|
if (action >= PEEP_ACTION_NONE_1)
|
|
{
|
|
if ((0xFFFF & scenario_rand()) < 936)
|
|
{
|
|
Invalidate();
|
|
|
|
action = PEEP_ACTION_TAKE_PHOTO;
|
|
action_frame = 0;
|
|
action_sprite_image_offset = 0;
|
|
|
|
UpdateCurrentActionSpriteType();
|
|
Invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (peep_flags & PEEP_FLAGS_PAINTING)
|
|
{
|
|
if (action >= PEEP_ACTION_NONE_1)
|
|
{
|
|
if ((0xFFFF & scenario_rand()) < 936)
|
|
{
|
|
Invalidate();
|
|
|
|
action = PEEP_ACTION_DRAW_PICTURE;
|
|
action_frame = 0;
|
|
action_sprite_image_offset = 0;
|
|
|
|
UpdateCurrentActionSpriteType();
|
|
Invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
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 ebp = 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, ebp);
|
|
}
|
|
}
|
|
}
|
|
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 bp = 0;
|
|
|
|
if (pos_stnd != 32)
|
|
{
|
|
item_standard_flags &= ~(1u << pos_stnd);
|
|
bp = 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);
|
|
bp = 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, bp);
|
|
}
|
|
}
|
|
|
|
// 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())
|
|
{
|
|
TileElement* tile_element = map_get_surface_element_at({ next_x, next_y });
|
|
|
|
int32_t water_height = tile_element->AsSurface()->GetWaterHeight();
|
|
if (water_height)
|
|
{
|
|
Invalidate();
|
|
water_height *= 16;
|
|
MoveTo(x, y, water_height);
|
|
Invalidate();
|
|
|
|
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);
|
|
|
|
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_type_offset != SPRITE_LIST_PEEP * 2)
|
|
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)
|
|
{
|
|
peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_NEW_RIDE, PEEP_THOUGHT_ITEM_NONE);
|
|
}
|
|
if (current_ride == RIDE_ID_NULL)
|
|
{
|
|
peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_SCENERY, PEEP_THOUGHT_ITEM_NONE);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x69185D
|
|
*/
|
|
void Guest::UpdateQueuing()
|
|
{
|
|
if (!CheckForPath())
|
|
{
|
|
RemoveFromQueue();
|
|
return;
|
|
}
|
|
Ride* ride = get_ride(current_ride);
|
|
if (ride->status == RIDE_STATUS_CLOSED || ride->status == RIDE_STATUS_TESTING)
|
|
{
|
|
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();
|
|
Invalidate();
|
|
}
|
|
if (time_in_queue >= 3500 && (0xFFFF & scenario_rand()) <= 93)
|
|
{
|
|
// Create the I have been waiting in line ages thought
|
|
peep_insert_new_thought(this, 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();
|
|
Invalidate();
|
|
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;
|
|
}
|
|
int16_t actionX = 0;
|
|
int16_t actionY = 0;
|
|
int16_t xy_distance;
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
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;
|
|
}
|
|
|
|
int16_t actionX = 0;
|
|
int16_t actionY = 0;
|
|
int16_t xy_distance;
|
|
if (UpdateAction(&actionX, &actionY, &xy_distance))
|
|
{
|
|
Invalidate();
|
|
MoveTo(actionX, actionY, z);
|
|
Invalidate();
|
|
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;
|
|
peep_sprite_remove(this);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* 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;
|
|
Invalidate();
|
|
|
|
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
|
|
int16_t actionX = 0;
|
|
int16_t actionY = 0;
|
|
int16_t xy_distance;
|
|
UpdateAction(&actionX, &actionY, &xy_distance);
|
|
|
|
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();
|
|
Invalidate();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((scenario_rand() & 0xFFFF) <= 655)
|
|
{
|
|
action = PEEP_ACTION_TAKE_PHOTO;
|
|
action_frame = 0;
|
|
action_sprite_image_offset = 0;
|
|
UpdateCurrentActionSpriteType();
|
|
Invalidate();
|
|
return;
|
|
}
|
|
|
|
if ((standing_flags & 1))
|
|
{
|
|
if ((scenario_rand() & 0xFFFF) <= 655)
|
|
{
|
|
action = PEEP_ACTION_WAVE;
|
|
action_frame = 0;
|
|
action_sprite_image_offset = 0;
|
|
UpdateCurrentActionSpriteType();
|
|
Invalidate();
|
|
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)
|
|
{
|
|
int16_t actionX, actionY, xy_distance;
|
|
UpdateAction(&actionX, &actionY, &xy_distance);
|
|
return;
|
|
}
|
|
|
|
TileElement* tileElement = map_get_first_element_at(next_x / 32, next_y / 32);
|
|
|
|
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 bp = 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, bp);
|
|
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 bp = 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, bp);
|
|
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;
|
|
}
|
|
|
|
if (!GetNextIsSurface() && !GetNextIsSloped())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* 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);
|
|
|
|
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_type_offset != SPRITE_LIST_PEEP * 2)
|
|
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);
|
|
|
|
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);
|
|
|
|
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_type_offset != SPRITE_LIST_PEEP * 2) || (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)
|
|
{
|
|
Ride* ride = get_ride(tileElement->AsTrack()->GetRideIndex());
|
|
|
|
// 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;
|
|
}
|
|
|
|
if (gRideClassifications[ride->type] != RIDE_CLASS_RIDE)
|
|
{
|
|
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)
|
|
{
|
|
Ride* ride = get_ride(tileElement->AsTrack()->GetRideIndex());
|
|
|
|
*rideToView = tileElement->AsTrack()->GetRideIndex();
|
|
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, *surfaceElement;
|
|
|
|
surfaceElement = map_get_surface_element_at({ peep->next_x, peep->next_y });
|
|
|
|
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->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 = 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->GetType() != TILE_ELEMENT_TYPE_WALL)
|
|
continue;
|
|
if (tileElement->GetDirectionWithOffset(2) != 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 = 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 = 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 = 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->GetDirectionWithOffset(2) != 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 = 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 = 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 = 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->GetType() != TILE_ELEMENT_TYPE_WALL)
|
|
continue;
|
|
if (tileElement->GetDirectionWithOffset(2) != 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 = 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_PIZZA },
|
|
{ 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(SOUND_BALLOON_POP, 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 ((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);
|
|
}
|