diff --git a/src/openrct2/peep/Guest.cpp b/src/openrct2/peep/Guest.cpp index dd0b3e1a39..853b945079 100644 --- a/src/openrct2/peep/Guest.cpp +++ b/src/openrct2/peep/Guest.cpp @@ -32,6 +32,7 @@ #include "../ride/Station.h" #include "../ride/Track.h" #include "../scenario/Scenario.h" +#include "../util/Util.h" #include "../world/Climate.h" #include "../world/Footpath.h" #include "../world/LargeScenery.h" @@ -211,12 +212,661 @@ static constexpr const uint8 item_extra_litter[32] = { LITTER_TYPE_RUBBISH, // PEEP_ITEM_ROAST_SAUSAGE LITTER_TYPE_EMPTY_BOWL_BLUE, // PEEP_ITEM_EMPTY_BOWL_BLUE }; + +/** rct2: 0x009822F4, 0x00982310 */ +static constexpr const uint8 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 uint8 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 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 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(rct_peep * peep, sint32 rideIndex); -static void peep_ride_is_too_intense(rct_peep * peep, sint32 rideIndex, bool peepAtRide); -static void peep_reset_ride_heading(rct_peep * peep); -static void peep_tried_to_enter_full_queue(rct_peep * peep, sint32 rideIndex); +static bool peep_has_voucher_for_free_ride(rct_peep * peep, sint32 rideIndex); +static void peep_ride_is_too_intense(rct_peep * peep, sint32 rideIndex, bool peepAtRide); +static void peep_reset_ride_heading(rct_peep * peep); +static void peep_tried_to_enter_full_queue(rct_peep * peep, sint32 rideIndex); +static sint16 peep_calculate_ride_satisfaction(rct_peep * peep, Ride * ride); +static void peep_update_favourite_ride(rct_peep * peep, Ride * ride); +static sint16 peep_calculate_ride_value_satisfaction(rct_peep * peep, Ride * ride); +static sint16 peep_calculate_ride_intensity_nausea_satisfaction(rct_peep * peep, Ride * ride); +static void peep_update_ride_nausea_growth(rct_peep * peep, Ride * ride); +static bool peep_should_go_on_ride_again(rct_peep * peep, Ride * ride); +static bool peep_should_preferred_intensity_increase(rct_peep * peep); +static bool peep_really_liked_ride(rct_peep * peep, Ride * ride); +static uint8 peep_assess_surroundings(sint16 centre_x, sint16 centre_y, sint16 centre_z); +static void peep_update_hunger(rct_peep * peep); +static void peep_decide_whether_to_leave_park(rct_peep * peep); +static void peep_leave_park(rct_peep * peep); +static void peep_head_for_nearest_ride_type(rct_peep * peep, sint32 rideType); +static void peep_head_for_nearest_ride_with_flags(rct_peep * peep, sint32 rideTypeFlags); + +void rct_peep::Tick128UpdateGuest(sint32 index) +{ + if ((uint32)(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) + { + uint8 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) + { + + uint8 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 = Math::Min(PEEP_MAX_HAPPINESS, happiness_target + 45); + } + } + } + } + + peep_update_sprite_type(this); + + if (state == PEEP_STATE_ON_RIDE || state == PEEP_STATE_ENTERING_RIDE) + { + time_on_ride = Math::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 = Math::Max(0, happiness_target - 5); + + if (time_on_ride > 22) + { + Ride * ride = get_ride(current_ride); + + uint8 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 == 0xFF) + { + + uint32 time_duration = gScenarioTicks - time_in_park; + time_duration /= 2048; + + if (time_duration >= 5) + { + PickRideToGoOn(); + + if (guest_heading_to_ride_id == 0xFF) + { + happiness_target = Math::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)(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 num_thoughts = 0; + uint8 possible_thoughts[5] = { 0 }; + + 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) + { + uint8 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; + } + } + } + } + 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) + { + uint8 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 = Math::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. */ + rct_tile_element * 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 (footpath_element_has_path_scenery(tileElement) && !footpath_element_path_scenery_is_ghost(tileElement)) + { + uint8 pathSceneryIndex = footpath_element_get_path_scenery_index(tileElement); + rct_scenery_entry * sceneryEntry = get_footpath_item_entry(pathSceneryIndex); + if (sceneryEntry->path_bit.flags & PATH_BIT_FLAG_IS_QUEUE_SCREEN) + { + found = true; + } + } + break; + } while (!tile_element_is_last_for_tile(tileElement++)); + + 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 = Math::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; + } + + loc_68F9F3: + // Idle peep happiness tends towards 127 (50%). + if (happiness_target >= 128) + happiness_target--; + else + happiness_target++; + + nausea_target = Math::Max(nausea_target - 2, 0); + + if (energy <= 50) + { + energy = Math::Max(energy - 2, 0); + } + + if (hunger < 10) + { + hunger = Math::Max(hunger - 1, 0); + } + + if (thirst < 10) + { + thirst = Math::Max(thirst - 1, 0); + } + + if (toilet >= 195) + { + toilet--; + } + + if (state == PEEP_STATE_WALKING && nausea_target >= 128) + { + + if ((scenario_rand() & 0xFF) <= (uint8)((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 = Math::Max(time_to_consume - 3, 0); + + if (HasDrink()) + { + thirst = Math::Min(thirst + 7, 255); + } + else + { + hunger = Math::Min(hunger + 7, 255); + thirst = Math::Max(thirst - 3, 0); + toilet = Math::Min(toilet + 2, 255); + } + + if (time_to_consume == 0) + { + sint32 chosen_food = bitscanforward(HasFoodStandardFlag()); + if (chosen_food != -1) + { + item_standard_flags &= ~(1 << chosen_food); + + uint8 discard_container = peep_item_containers[chosen_food]; + if (discard_container != 0xFF) + { + item_standard_flags |= (1 << discard_container); + } + + window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY; + peep_update_sprite_type(this); + } + else + { + chosen_food = bitscanforward(HasFoodExtraFlag()); + if (chosen_food != -1) + { + item_extra_flags &= ~(1 << chosen_food); + uint8 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; + peep_update_sprite_type(this); + } + } + } + } + + uint8 newEnergy = energy; + uint8 newTargetEnergy = energy_target; + if (newEnergy >= newTargetEnergy) + { + newEnergy -= 2; + if (newEnergy < newTargetEnergy) + newEnergy = newTargetEnergy; + } + else + { + newEnergy = Math::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 = Math::Min(static_cast(PEEP_MAX_ENERGY), newEnergy); + + if (newEnergy != energy) + { + energy = newEnergy; + window_invalidate_flags |= PEEP_INVALIDATE_PEEP_2; + } + + uint8 newHappiness = happiness; + uint8 newHappinessGrowth = happiness_target; + if (newHappiness >= newHappinessGrowth) + { + newHappiness = Math::Max(newHappiness - 4, 0); + if (newHappiness < newHappinessGrowth) + newHappiness = newHappinessGrowth; + } + else + { + newHappiness = Math::Min(255, newHappiness + 4); + if (newHappiness > newHappinessGrowth) + newHappiness = newHappinessGrowth; + } + + if (newHappiness != happiness) + { + happiness = newHappiness; + window_invalidate_flags |= PEEP_INVALIDATE_PEEP_2; + } + + uint8 newNausea = nausea; + uint8 newNauseaGrowth = nausea_target; + if (newNausea >= newNauseaGrowth) + { + newNausea = Math::Max(newNausea - 4, 0); + if (newNausea < newNauseaGrowth) + newNausea = newNauseaGrowth; + } + else + { + newNausea = Math::Min(255, newNausea + 4); + if (newNausea > newNauseaGrowth) + newNausea = newNauseaGrowth; + } + + if (newNausea != nausea) + { + nausea = newNausea; + window_invalidate_flags |= PEEP_INVALIDATE_PEEP_2; + } +} /** * @@ -396,6 +1046,545 @@ void rct_peep::RemoveFromQueue() } } +bool rct_peep::HasItem(sint32 peepItem) const +{ + if (peepItem < 32) + { + return item_standard_flags & (1u << peepItem); + } + else + { + return item_extra_flags & (1u << (peepItem - 32)); + } +} + +sint32 rct_peep::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); +} + +sint32 rct_peep::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 rct_peep::HasDrinkStandardFlag() const +{ + return item_standard_flags & (PEEP_ITEM_DRINK | PEEP_ITEM_COFFEE | PEEP_ITEM_LEMONADE); +} + +bool rct_peep::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 rct_peep::HasDrink() const +{ + return HasDrinkStandardFlag() || HasDrinkExtraFlag(); +} + +sint32 rct_peep::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); +} + +sint32 rct_peep::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 rct_peep::HasEmptyContainer() const +{ + return HasEmptyContainerStandardFlag() || HasEmptyContainerExtraFlag(); +} + +/** + * + * rct2: 0x69C308 + * Check if lost. + */ +void rct_peep::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 = Math::Max(happiness_target - 30, 0); +} + +/** + * + * rct2: 0x69C26B + * Check if cant find ride. + */ +void rct_peep::CheckCantFindRide() +{ + if (guest_heading_to_ride_id == 0xFF) + 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 = Math::Max(happiness_target - 30, 0); + } + + peep_is_lost_countdown--; + if (peep_is_lost_countdown != 0) + return; + + guest_heading_to_ride_id = 0xFF; + 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 rct_peep::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 = Math::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 rct_peep::DecideAndBuyItem(uint8 rideIndex, sint32 shopItem, money32 price) +{ + Ride * ride = get_ride(rideIndex); + 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)) + { + sint32 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" + uint8 thought_type = + (shopItem >= 32 ? (PEEP_THOUGHT_TYPE_PHOTO2_MUCH + (shopItem - 32)) + : (PEEP_THOUGHT_TYPE_BALLOON_MUCH + shopItem)); + peep_insert_new_thought(this, thought_type, rideIndex); + return false; + } + } + else + { + itemValue -= price; + itemValue = Math::Max(8, itemValue); + + if (!(gParkFlags & PARK_FLAGS_NO_MONEY)) + { + if (itemValue >= (money32)(scenario_rand() & 0x07)) + { + // "This x is a really good value" + uint8 thought_item = + (shopItem >= 32 ? (PEEP_THOUGHT_TYPE_PHOTO2 + (shopItem - 32)) + : (PEEP_THOUGHT_TYPE_BALLOON + shopItem)); + peep_insert_new_thought(this, thought_item, rideIndex); + } + } + + sint32 happinessGrowth = itemValue * 4; + happiness_target = Math::Min((happiness_target + happinessGrowth), PEEP_MAX_HAPPINESS); + happiness = Math::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 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_main[0]; + + if (shopItem == SHOP_ITEM_HAT) + hat_colour = ride->track_colour_main[0]; + + if (shopItem == SHOP_ITEM_BALLOON) + balloon_colour = ride->track_colour_main[0]; + + if (shopItem == SHOP_ITEM_UMBRELLA) + umbrella_colour = ride->track_colour_main[0]; + + if (shopItem == SHOP_ITEM_MAP) + peep_reset_pathfind_goal(this); + + uint16 consumptionTime = item_consumption_time[shopItem]; + time_to_consume = Math::Min((time_to_consume + consumptionTime), 255); + + if (shopItem == SHOP_ITEM_PHOTO) + photo1_ride_ref = rideIndex; + + if (shopItem == SHOP_ITEM_PHOTO2) + photo2_ride_ref = rideIndex; + + if (shopItem == SHOP_ITEM_PHOTO3) + photo3_ride_ref = rideIndex; + + if (shopItem == SHOP_ITEM_PHOTO4) + photo4_ride_ref = rideIndex; + + window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY; + peep_update_sprite_type(this); + if (peep_flags & PEEP_FLAGS_TRACKING) + { + set_format_arg(0, rct_string_id, name_string_idx); + set_format_arg(2, uint32, 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 rct_peep::OnEnterRide(uint8 rideIndex) +{ + Ride * ride = get_ride(rideIndex); + + // Calculate how satisfying the ride is for the peep. Can range from -140 to +105. + sint16 satisfaction = peep_calculate_ride_satisfaction(this, ride); + + // Update the satisfaction stat of the ride. + uint8 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(current_ride); + peep_update_favourite_ride(this, ride); + happiness_target = Math::Clamp(0, happiness_target + satisfaction, PEEP_MAX_HAPPINESS); + peep_update_ride_nausea_growth(this, ride); +} + +/** + * + * rct2: 0x0069576E + */ +void rct_peep::OnExitRide(uint8 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); + + rct_window * w = window_find_by_number(WC_PEEP, sprite_index); + if (w != nullptr) + { + window_event_invalidate_call(w); + widget_invalidate(w, WC_PEEP__WIDX_ACTION_LBL); + } + } + + 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); + + sint32 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; +} + +/** + * + * rct2: 0x0069A512 + */ +void rct_peep::RemoveFromRide() +{ + if (state == PEEP_STATE_QUEUING) + { + RemoveFromQueue(); + } + StateReset(); +} + +/** + * To simplify check of 0x36BA3E0 and 0x11FF78 + * returns false on no food. + */ +bool rct_peep::HasFood() const +{ + return HasFoodStandardFlag() || HasFoodExtraFlag(); +} + /** * * rct2: 0x00695DD2 @@ -404,15 +1593,15 @@ void rct_peep::PickRideToGoOn() { Ride * ride; - if (this->state != PEEP_STATE_WALKING) + if (state != PEEP_STATE_WALKING) return; - if (this->guest_heading_to_ride_id != 255) + if (guest_heading_to_ride_id != 255) return; - if (this->peep_flags & PEEP_FLAGS_LEAVING_PARK) + if (peep_flags & PEEP_FLAGS_LEAVING_PARK) return; if (HasFood()) return; - if (this->x == LOCATION_NULL) + if (x == LOCATION_NULL) return; uint32 rideConsideration[8]{}; @@ -420,7 +1609,7 @@ void rct_peep::PickRideToGoOn() // 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 (this->item_standard_flags & PEEP_ITEM_MAP) + if (item_standard_flags & PEEP_ITEM_MAP) { // Consider rides that peep hasn't been on yet sint32 i; @@ -435,8 +1624,8 @@ void rct_peep::PickRideToGoOn() else { // Take nearby rides into consideration - sint32 cx = floor2(this->x, 32); - sint32 cy = floor2(this->y, 32); + sint32 cx = floor2(x, 32); + sint32 cy = floor2(y, 32); for (sint32 tileX = cx - 320; tileX <= cx + 320; tileX += 32) { for (sint32 tileY = cy - 320; tileY <= cy + 320; tileY += 32) @@ -509,12 +1698,12 @@ void rct_peep::PickRideToGoOn() return; // Head to that ride - this->guest_heading_to_ride_id = mostExcitingRideIndex; - this->peep_is_lost_countdown = 200; + guest_heading_to_ride_id = mostExcitingRideIndex; + peep_is_lost_countdown = 200; peep_reset_pathfind_goal(this); // Invalidate windows - rct_window * w = window_find_by_number(WC_PEEP, this->sprite_index); + rct_window * w = window_find_by_number(WC_PEEP, sprite_index); if (w != nullptr) { window_event_invalidate_call(w); @@ -522,7 +1711,7 @@ void rct_peep::PickRideToGoOn() } // Make peep look at their map if they have one - if (this->item_standard_flags & PEEP_ITEM_MAP) + if (item_standard_flags & PEEP_ITEM_MAP) { ReadMap(); } @@ -548,7 +1737,7 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue if (!(RideData4[ride->type].flags & RIDE_TYPE_FLAG4_TRANSPORT_RIDE) || ride->value == 0xFFFF || ride_get_price(ride) != 0) { - if (this->peep_flags & PEEP_FLAGS_LEAVING_PARK) + if (peep_flags & PEEP_FLAGS_LEAVING_PARK) { ChoseNotToGoOnRide(rideIndex, peepAtRide, false); return false; @@ -586,10 +1775,10 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue if (ride->last_peep_in_queue[entranceNum] != 0xFFFF) { rct_peep * lastPeepInQueue = GET_PEEP(ride->last_peep_in_queue[entranceNum]); - if (abs(lastPeepInQueue->z - this->z) <= 6) + if (abs(lastPeepInQueue->z - z) <= 6) { - sint32 dx = abs(lastPeepInQueue->x - this->x); - sint32 dy = abs(lastPeepInQueue->y - this->y); + sint32 dx = abs(lastPeepInQueue->x - x); + sint32 dy = abs(lastPeepInQueue->y - y); sint32 maxD = Math::Max(dx, dy); // Unlike normal paths, peeps cannot overlap when queueing for a ride. @@ -616,7 +1805,7 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue money16 ridePrice = ride_get_price(ride); if (!(RideData4[ride->type].flags & RIDE_TYPE_FLAG4_TRANSPORT_RIDE) || ride->value == 0xFFFF || ridePrice != 0) { - if (this->previous_ride == rideIndex) + if (previous_ride == rideIndex) { ChoseNotToGoOnRide(rideIndex, peepAtRide, false); return false; @@ -626,11 +1815,11 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue if (ridePrice != 0 && !peep_has_voucher_for_free_ride(this, rideIndex)) { - if (ridePrice > this->cash_in_pocket) + if (ridePrice > cash_in_pocket) { if (peepAtRide) { - if (this->cash_in_pocket <= 0) + if (cash_in_pocket <= 0) { peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_SPENT_MONEY, PEEP_THOUGHT_ITEM_NONE); } @@ -645,14 +1834,14 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue } // If happy enough, peeps will ignore the fact that a ride has recently crashed. - if (ride->last_crash_type != RIDE_CRASH_TYPE_NONE && this->happiness < 225) + if (ride->last_crash_type != RIDE_CRASH_TYPE_NONE && happiness < 225) { if (peepAtRide) { peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_NOT_SAFE, rideIndex); - if (this->happiness_target >= 64) + if (happiness_target >= 64) { - this->happiness_target -= 8; + happiness_target -= 8; } ride_update_popularity(ride, 0); } @@ -664,7 +1853,7 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue { // 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 (rideIndex == this->guest_heading_to_ride_id) + if (rideIndex == guest_heading_to_ride_id) { if (ride->intensity > RIDE_RATING(10, 00) && !gCheatsIgnoreRideIntensity) { @@ -680,9 +1869,9 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue if (peepAtRide) { peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_NOT_WHILE_RAINING, rideIndex); - if (this->happiness_target >= 64) + if (happiness_target >= 64) { - this->happiness_target -= 8; + happiness_target -= 8; } ride_update_popularity(ride, 0); } @@ -695,16 +1884,16 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue // 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 = Math::Min((this->intensity >> 4) * 100, 1000) + this->happiness; - ride_rating minIntensity = ((this->intensity & 0x0F) * 100) - this->happiness; + ride_rating maxIntensity = Math::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, rideIndex); - if (this->happiness_target >= 64) + if (happiness_target >= 64) { - this->happiness_target -= 8; + happiness_target -= 8; } ride_update_popularity(ride, 0); } @@ -718,16 +1907,16 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue } // Nausea calculations. - ride_rating maxNausea = NauseaMaximumThresholds[(this->nausea_tolerance & 3)] + this->happiness; + ride_rating maxNausea = NauseaMaximumThresholds[(nausea_tolerance & 3)] + happiness; if (ride->nausea > maxNausea) { if (peepAtRide) { peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_SICKENING, rideIndex); - if (this->happiness_target >= 64) + if (happiness_target >= 64) { - this->happiness_target -= 8; + happiness_target -= 8; } ride_update_popularity(ride, 0); } @@ -736,7 +1925,7 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue } // Very nauseous peeps will only go on very gentle rides. - if (ride->nausea >= FIXED_2DP(1, 40) && this->nausea > 160) + if (ride->nausea >= FIXED_2DP(1, 40) && nausea > 160) { ChoseNotToGoOnRide(rideIndex, peepAtRide, false); return false; @@ -772,7 +1961,7 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue { // The amount peeps are willing to pay is decreased by 75% if they had to pay to enter the park. - if (this->peep_flags & PEEP_FLAGS_HAS_PAID_FOR_PARK_ENTRY) + if (peep_flags & PEEP_FLAGS_HAS_PAID_FOR_PARK_ENTRY) value /= 4; // Peeps won't pay more than twice the value of the ride. @@ -782,9 +1971,9 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue if (peepAtRide) { peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_BAD_VALUE, rideIndex); - if (this->happiness_target >= 60) + if (happiness_target >= 60) { - this->happiness_target -= 16; + happiness_target -= 16; } ride_update_popularity(ride, 0); } @@ -797,7 +1986,7 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue { if (!(gParkFlags & PARK_FLAGS_NO_MONEY)) { - if (!(this->peep_flags & PEEP_FLAGS_HAS_PAID_FOR_PARK_ENTRY)) + if (!(peep_flags & PEEP_FLAGS_HAS_PAID_FOR_PARK_ENTRY)) { peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_GOOD_VALUE, rideIndex); } @@ -812,7 +2001,7 @@ bool rct_peep::ShouldGoOnRide(sint32 rideIndex, sint32 entranceNum, bool atQueue ride_update_popularity(ride, 1); } - if (rideIndex == this->guest_heading_to_ride_id) + if (rideIndex == guest_heading_to_ride_id) { peep_reset_ride_heading(this); } @@ -830,53 +2019,53 @@ bool rct_peep::ShouldGoToShop(sint32 rideIndex, bool peepAtShop) Ride * ride = get_ride(rideIndex); // Peeps won't go to the same shop twice in a row. - if (rideIndex == this->previous_ride) + if (rideIndex == previous_ride) { - this->ChoseNotToGoOnRide(rideIndex, peepAtShop, true); + ChoseNotToGoOnRide(rideIndex, peepAtShop, true); return false; } if (ride->type == RIDE_TYPE_TOILETS) { - if (this->toilet < 70) + if (toilet < 70) { - this->ChoseNotToGoOnRide(rideIndex, peepAtShop, true); + ChoseNotToGoOnRide(rideIndex, 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 > this->toilet) + if (ride->price * 40 > toilet) { if (peepAtShop) { peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_NOT_PAYING, rideIndex); - if (this->happiness_target >= 60) + if (happiness_target >= 60) { - this->happiness_target -= 16; + happiness_target -= 16; } ride_update_popularity(ride, 0); } - this->ChoseNotToGoOnRide(rideIndex, peepAtShop, true); + ChoseNotToGoOnRide(rideIndex, peepAtShop, true); return false; } } if (ride->type == RIDE_TYPE_FIRST_AID) { - if (this->nausea < 128) + if (nausea < 128) { - this->ChoseNotToGoOnRide(rideIndex, peepAtShop, true); + ChoseNotToGoOnRide(rideIndex, peepAtShop, true); return false; } } // Basic price checks - if (ride->price != 0 && ride->price > this->cash_in_pocket) + if (ride->price != 0 && ride->price > cash_in_pocket) { if (peepAtShop) { - if (this->cash_in_pocket <= 0) + if (cash_in_pocket <= 0) { peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_SPENT_MONEY, PEEP_THOUGHT_ITEM_NONE); } @@ -885,14 +2074,14 @@ bool rct_peep::ShouldGoToShop(sint32 rideIndex, bool peepAtShop) peep_insert_new_thought(this, PEEP_THOUGHT_TYPE_CANT_AFFORD_0, rideIndex); } } - this->ChoseNotToGoOnRide(rideIndex, peepAtShop, true); + ChoseNotToGoOnRide(rideIndex, peepAtShop, true); return false; } if (peepAtShop) { ride_update_popularity(ride, 1); - if (rideIndex == this->guest_heading_to_ride_id) + if (rideIndex == guest_heading_to_ride_id) { peep_reset_ride_heading(this); } @@ -900,6 +2089,67 @@ bool rct_peep::ShouldGoToShop(sint32 rideIndex, bool peepAtShop) return true; } +// Used when no logging to an expend type required +void rct_peep::SpendMoney(money32 amount) +{ + money16 unused; + SpendMoney(unused, amount); +} + +/** + * + * rct2: 0x0069926C + * Expend type was previously an offset saved in 0x00F1AEC0 + */ +void rct_peep::SpendMoney(money16 & peep_expend_type, money32 amount) +{ + assert(!(gParkFlags & PARK_FLAGS_NO_MONEY)); + + cash_in_pocket = Math::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 rct_peep::SetHasRidden(sint32 rideIndex) +{ + rides_been_on[rideIndex / 8] |= 1 << (rideIndex % 8); + Ride * ride = get_ride(rideIndex); + SetHasRiddenRideType(ride->type); +} + +bool rct_peep::HasRidden(sint32 rideIndex) const +{ + return rides_been_on[rideIndex / 8] & (1 << (rideIndex % 8)); +} + +void rct_peep::SetHasRiddenRideType(sint32 rideType) +{ + ride_types_been_on[rideType / 8] |= 1 << (rideType % 8); +} + +bool rct_peep::HasRiddenRideType(sint32 rideType) const +{ + return ride_types_been_on[rideType / 8] & (1 << (rideType % 8)); +} + void rct_peep::ChoseNotToGoOnRide(sint32 rideIndex, bool peepAtRide, bool updateLastRide) { if (peepAtRide && updateLastRide) @@ -1197,6 +2447,729 @@ static bool peep_check_ride_price_at_entrance(rct_peep * peep, Ride * ride, mone 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 sint16 peep_calculate_ride_satisfaction(rct_peep * peep, Ride * ride) +{ + sint16 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(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(rct_peep * peep, Ride * ride) +{ + peep->peep_flags &= ~PEEP_FLAGS_RIDE_SHOULD_BE_MARKED_AS_FAVOURITE; + uint8 peepRideRating = Math::Clamp(0, (ride->excitement / 4) + peep->happiness, 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 sint16 peep_calculate_ride_value_satisfaction(rct_peep * peep, Ride * ride) +{ + if (gParkFlags & PARK_FLAGS_NO_MONEY) + { + return -30; + } + + if (ride->value == 0xFFFF) + { + 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 sint16 peep_calculate_ride_intensity_nausea_satisfaction(rct_peep * peep, Ride * ride) +{ + if (!ride_has_ratings(ride)) + { + return 70; + } + + uint8 intensitySatisfaction = 3; + uint8 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 highestSatisfaction = Math::Max(intensitySatisfaction, nauseaSatisfaction); + uint8 lowestSatisfaction = Math::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(rct_peep * peep, Ride * ride) +{ + uint32 nauseaMultiplier = Math::Clamp(64, 256 - peep->happiness_target, 200); + uint32 nauseaGrowthRateChange = (ride->nausea * nauseaMultiplier) / 512; + nauseaGrowthRateChange *= Math::Max(static_cast(128), peep->hunger) / 64; + nauseaGrowthRateChange >>= (peep->nausea_tolerance & 3); + peep->nausea_target = (uint8)Math::Min(peep->nausea_target + nauseaGrowthRateChange, 255u); +} + +static bool peep_should_go_on_ride_again(rct_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 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(rct_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(rct_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 uint8 peep_assess_surroundings(sint16 centre_x, sint16 centre_y, sint16 centre_z) +{ + if ((tile_element_height(centre_x, centre_y) & 0xFFFF) > centre_z) + return PEEP_THOUGHT_TYPE_NONE; + + uint16 num_scenery = 0; + uint16 num_fountains = 0; + uint16 nearby_music = 0; + uint16 num_rubbish = 0; + + sint16 initial_x = Math::Max(centre_x - 160, 0); + sint16 initial_y = Math::Max(centre_y - 160, 0); + sint16 final_x = Math::Min(centre_x + 160, 8192); + sint16 final_y = Math::Min(centre_y + 160, 8192); + + for (sint16 x = initial_x; x < final_x; x += 32) + { + for (sint16 y = initial_y; y < final_y; y += 32) + { + rct_tile_element * 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 (!footpath_element_has_path_scenery(tileElement)) + break; + + scenery = get_footpath_item_entry(footpath_element_get_path_scenery_index(tileElement)); + if (scenery == nullptr) + { + return PEEP_THOUGHT_TYPE_NONE; + } + if (footpath_element_path_scenery_is_ghost(tileElement)) + break; + + if (scenery->path_bit.flags & (PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER | PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW)) + { + num_fountains++; + break; + } + if (tileElement->flags & TILE_ELEMENT_FLAG_BROKEN) + { + 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(track_element_get_ride_index(tileElement)); + 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 (!tile_element_is_last_for_tile(tileElement++)); + } + } + + rct_litter * litter; + for (uint16 sprite_idx = gSpriteListHead[SPRITE_LIST_LITTER]; sprite_idx != SPRITE_INDEX_NULL; sprite_idx = litter->next) + { + litter = &(get_sprite(sprite_idx)->litter); + + sint16 dist_x = abs(litter->x - centre_x); + sint16 dist_y = abs(litter->y - centre_y); + if (Math::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(rct_peep * peep) +{ + if (peep->hunger >= 3) + { + peep->hunger -= 2; + + peep->energy_target = Math::Min(peep->energy_target + 2, PEEP_MAX_ENERGY_TARGET); + peep->toilet = Math::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(rct_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(rct_peep * peep) +{ + peep->guest_heading_to_ride_id = 0xFF; + 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(rct_peep * peep, sint32 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 != 255) + { + ride = get_ride(peep->guest_heading_to_ride_id); + if (ride->type == rideType) + { + return; + } + } + + uint32 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 + sint32 i; + FOR_ALL_RIDES(i, ride) + { + if (ride->type == rideType) + { + rideConsideration[i >> 5] |= (1u << (i & 0x1F)); + } + } + } + else + { + // Take nearby rides into consideration + sint32 cx = floor2(peep->x, 32); + sint32 cy = floor2(peep->y, 32); + for (sint32 x = cx - 320; x <= cx + 320; x += 32) + { + for (sint32 y = cy - 320; y <= cy + 320; y += 32) + { + if (x >= 0 && y >= 0 && x < (256 * 32) && y < (256 * 32)) + { + rct_tile_element * tileElement = map_get_first_element_at(x >> 5, y >> 5); + do + { + if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK) + continue; + + sint32 rideIndex = track_element_get_ride_index(tileElement); + ride = get_ride(rideIndex); + if (ride->type == rideType) + { + rideConsideration[rideIndex >> 5] |= (1u << (rideIndex & 0x1F)); + } + } while (!tile_element_is_last_for_tile(tileElement++)); + } + } + } + } + + // Filter the considered rides + uint8 potentialRides[256]; + uint8 * nextPotentialRide = &potentialRides[0]; + sint32 numPotentialRides = 0; + for (sint32 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(i, 0, false, true)) + { + *nextPotentialRide++ = i; + numPotentialRides++; + } + } + } + + // Pick the closest ride + sint32 closestRideIndex = -1; + sint32 closestRideDistance = std::numeric_limits::max(); + for (sint32 i = 0; i < numPotentialRides; i++) + { + ride = get_ride(potentialRides[i]); + sint32 rideX = ride->station_starts[0].x * 32; + sint32 rideY = ride->station_starts[0].y * 32; + sint32 distance = abs(rideX - peep->x) + abs(rideY - peep->y); + if (distance < closestRideDistance) + { + closestRideIndex = potentialRides[i]; + closestRideDistance = distance; + } + } + if (closestRideIndex == -1) + 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); + widget_invalidate(w, WC_PEEP__WIDX_ACTION_LBL); + } + + peep->time_lost = 0; +} + +/** + * + * rct2: 0x006958D0 + */ +static void peep_head_for_nearest_ride_with_flags(rct_peep * peep, sint32 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 != 255) + { + 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 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 + sint32 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 + sint32 cx = floor2(peep->x, 32); + sint32 cy = floor2(peep->y, 32); + for (sint32 x = cx - 320; x <= cx + 320; x += 32) + { + for (sint32 y = cy - 320; y <= cy + 320; y += 32) + { + if (x >= 0 && y >= 0 && x < (256 * 32) && y < (256 * 32)) + { + rct_tile_element * tileElement = map_get_first_element_at(x >> 5, y >> 5); + do + { + if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK) + continue; + + sint32 rideIndex = track_element_get_ride_index(tileElement); + ride = get_ride(rideIndex); + if (ride_type_has_flag(ride->type, rideTypeFlags)) + { + rideConsideration[rideIndex >> 5] |= (1u << (rideIndex & 0x1F)); + } + } while (!tile_element_is_last_for_tile(tileElement++)); + } + } + } + } + + // Filter the considered rides + uint8 potentialRides[256]; + uint8 * nextPotentialRide = &potentialRides[0]; + sint32 numPotentialRides = 0; + for (sint32 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(i, 0, false, true)) + { + *nextPotentialRide++ = i; + numPotentialRides++; + } + } + } + + // Pick the closest ride + sint32 closestRideIndex = -1; + sint32 closestRideDistance = std::numeric_limits::max(); + for (sint32 i = 0; i < numPotentialRides; i++) + { + ride = get_ride(potentialRides[i]); + sint32 rideX = ride->station_starts[0].x * 32; + sint32 rideY = ride->station_starts[0].y * 32; + sint32 distance = abs(rideX - peep->x) + abs(rideY - peep->y); + if (distance < closestRideDistance) + { + closestRideIndex = potentialRides[i]; + closestRideDistance = distance; + } + } + if (closestRideIndex == -1) + 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 diff --git a/src/openrct2/peep/Peep.cpp b/src/openrct2/peep/Peep.cpp index 2da15e8a77..c1d0548f69 100644 --- a/src/openrct2/peep/Peep.cpp +++ b/src/openrct2/peep/Peep.cpp @@ -86,15 +86,11 @@ static void * _crowdSoundChannel = nullptr; static void peep_128_tick_update(rct_peep * peep, sint32 index); static bool peep_should_find_bench(rct_peep * peep); -static void peep_update_favourite_ride(rct_peep * peep, Ride * ride); -static sint16 peep_calculate_ride_satisfaction(rct_peep * peep, Ride * ride); static void peep_update_ride_nausea_growth(rct_peep * peep, Ride * ride); static void peep_ride_is_too_intense(rct_peep * peep, sint32 rideIndex, bool peepAtRide); static void peep_tried_to_enter_full_queue(rct_peep * peep, sint32 rideIndex); static bool peep_find_ride_to_look_at(rct_peep * peep, uint8 edge, uint8 * rideToView, uint8 * rideSeatToView); static void peep_easter_egg_peep_interactions(rct_peep * peep); -static void peep_head_for_nearest_ride_type(rct_peep * peep, sint32 rideType); -static void peep_head_for_nearest_ride_with_flags(rct_peep * peep, sint32 rideTypeFlags); static void peep_give_real_name(rct_peep * peep); static void peep_release_balloon(rct_peep * peep, sint16 spawn_height); @@ -353,7 +349,7 @@ static uint8 PeepActionToSpriteTypeMap[] = { PEEP_ACTION_SPRITE_TYPE_WITHDRAW_MONEY }; -static constexpr const bool SpriteTypeToSlowWalkMap[] = { +const bool gSpriteTypeToSlowWalkMap[] = { false, false, false, false, false, false, false, false, false, false, false, true, false, false, true, true, true, true, true, false, true, false, true, true, @@ -362,11 +358,6 @@ static constexpr const bool SpriteTypeToSlowWalkMap[] = { false, true, true, true, true, true, true, true, }; -// 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 -}; - static constexpr const ride_rating NauseaMaximumThresholds[] = { 300, 600, 800, 1000 }; @@ -459,359 +450,6 @@ void peep_update_all() } } -/** - * - * rct2: 0x0069BC9A - */ -static uint8 peep_assess_surroundings(sint16 centre_x, sint16 centre_y, sint16 centre_z) -{ - if ((tile_element_height(centre_x, centre_y) & 0xFFFF) > centre_z) - return PEEP_THOUGHT_TYPE_NONE; - - uint16 num_scenery = 0; - uint16 num_fountains = 0; - uint16 nearby_music = 0; - uint16 num_rubbish = 0; - - sint16 initial_x = Math::Max(centre_x - 160, 0); - sint16 initial_y = Math::Max(centre_y - 160, 0); - sint16 final_x = Math::Min(centre_x + 160, 8192); - sint16 final_y = Math::Min(centre_y + 160, 8192); - - for (sint16 x = initial_x; x < final_x; x += 32) - { - for (sint16 y = initial_y; y < final_y; y += 32) - { - rct_tile_element * 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 (!footpath_element_has_path_scenery(tileElement)) - break; - - scenery = get_footpath_item_entry(footpath_element_get_path_scenery_index(tileElement)); - if (scenery == nullptr) - { - return PEEP_THOUGHT_TYPE_NONE; - } - if (footpath_element_path_scenery_is_ghost(tileElement)) - break; - - if (scenery->path_bit.flags & (PATH_BIT_FLAG_JUMPING_FOUNTAIN_WATER | PATH_BIT_FLAG_JUMPING_FOUNTAIN_SNOW)) - { - num_fountains++; - break; - } - if (tileElement->flags & TILE_ELEMENT_FLAG_BROKEN) - { - 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(track_element_get_ride_index(tileElement)); - 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 (!tile_element_is_last_for_tile(tileElement++)); - } - } - - rct_litter * litter; - for (uint16 sprite_idx = gSpriteListHead[SPRITE_LIST_LITTER]; sprite_idx != SPRITE_INDEX_NULL; sprite_idx = litter->next) - { - litter = &(get_sprite(sprite_idx)->litter); - - sint16 dist_x = abs(litter->x - centre_x); - sint16 dist_y = abs(litter->y - centre_y); - if (Math::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(rct_peep * peep) -{ - if (peep->hunger >= 3) - { - peep->hunger -= 2; - - peep->energy_target = Math::Min(peep->energy_target + 2, PEEP_MAX_ENERGY_TARGET); - peep->toilet = Math::Min(peep->toilet + 1, 255); - } -} - -/** - * - * rct2: 0x0068F93E - */ -static void peep_leave_park(rct_peep * peep) -{ - peep->guest_heading_to_ride_id = 0xFF; - 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); -} - -/** - * 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(rct_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); -} - -// clang-format off -/** rct2: 0x009822F4, 0x00982310 */ -static constexpr const uint8 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_SUJEONGGWA - 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 uint8 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 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 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 -}; -// clang-format on - /** * * rct2: 0x0068F41A @@ -821,522 +459,11 @@ static void peep_128_tick_update(rct_peep * peep, sint32 index) { if (peep->type == PEEP_TYPE_STAFF) { - if (peep->staff_type != STAFF_TYPE_SECURITY) - return; - - uint8 sprite_type = PEEP_SPRITE_TYPE_SECURITY_ALT; - if (peep->state != PEEP_STATE_PATROLLING) - sprite_type = PEEP_SPRITE_TYPE_SECURITY; - - if (peep->sprite_type == sprite_type) - return; - - peep->sprite_type = sprite_type; - peep->action_sprite_image_offset = 0; - peep->no_action_frame_num = 0; - if (peep->action < PEEP_ACTION_NONE_1) - peep->action = PEEP_ACTION_NONE_2; - - peep->peep_flags &= ~PEEP_FLAGS_SLOW_WALK; - if (SpriteTypeToSlowWalkMap[sprite_type]) - { - peep->peep_flags |= PEEP_FLAGS_SLOW_WALK; - } - - peep->action_sprite_type = 0xFF; - peep->UpdateCurrentActionSpriteType(); - return; - } - - if ((uint32)(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->peep_flags & PEEP_FLAGS_CROWDED) - { - uint8 thought_type = crowded_thoughts[scenario_rand() & 0xF]; - if (thought_type != PEEP_THOUGHT_TYPE_NONE) - { - peep_insert_new_thought(peep, thought_type, PEEP_THOUGHT_ITEM_NONE); - } - } - - if (peep->peep_flags & PEEP_FLAGS_EXPLODE && peep->x != LOCATION_NULL) - { - if (peep->state == PEEP_STATE_WALKING || peep->state == PEEP_STATE_SITTING) - { - audio_play_sound_at_location(SOUND_CRASH, peep->x, peep->y, peep->z); - - sprite_misc_explosion_cloud_create(peep->x, peep->y, peep->z + 16); - sprite_misc_explosion_flare_create(peep->x, peep->y, peep->z + 16); - - peep->Remove(); - return; - } - else - { - peep->peep_flags &= ~PEEP_FLAGS_EXPLODE; - } - } - - if (peep->peep_flags & PEEP_FLAGS_HUNGER) - { - if (peep->hunger >= 15) - peep->hunger -= 15; - } - - if (peep->peep_flags & PEEP_FLAGS_BATHROOM) - { - if (peep->toilet <= 180) - peep->toilet += 50; - } - - if (peep->peep_flags & PEEP_FLAGS_HAPPINESS) - { - peep->happiness_target = 5; - } - - if (peep->peep_flags & PEEP_FLAGS_NAUSEA) - { - peep->nausea_target = 200; - if (peep->nausea <= 130) - peep->nausea = 130; - } - - if (peep->angriness != 0) - peep->angriness--; - - if (peep->state == PEEP_STATE_WALKING || peep->state == PEEP_STATE_SITTING) - { - peep->surroundings_thought_timeout++; - if (peep->surroundings_thought_timeout >= 18) - { - peep->surroundings_thought_timeout = 0; - if (peep->x != LOCATION_NULL) - { - - uint8 thought_type = peep_assess_surroundings(peep->x & 0xFFE0, peep->y & 0xFFE0, peep->z); - - if (thought_type != PEEP_THOUGHT_TYPE_NONE) - { - peep_insert_new_thought(peep, thought_type, PEEP_THOUGHT_ITEM_NONE); - peep->happiness_target = Math::Min(PEEP_MAX_HAPPINESS, peep->happiness_target + 45); - } - } - } - } - - peep_update_sprite_type(peep); - - if (peep->state == PEEP_STATE_ON_RIDE || peep->state == PEEP_STATE_ENTERING_RIDE) - { - peep->time_on_ride = Math::Min(255, peep->time_on_ride + 1); - - if (peep->peep_flags & PEEP_FLAGS_WOW) - { - peep_insert_new_thought(peep, PEEP_THOUGHT_TYPE_WOW2, PEEP_THOUGHT_ITEM_NONE); - } - - if (peep->time_on_ride > 15) - { - peep->happiness_target = Math::Max(0, peep->happiness_target - 5); - - if (peep->time_on_ride > 22) - { - Ride * ride = get_ride(peep->current_ride); - - uint8 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(peep, thought_type, peep->current_ride); - } - } - } - - if (peep->state == PEEP_STATE_WALKING && peep->outside_of_park == 0 && !(peep->peep_flags & PEEP_FLAGS_LEAVING_PARK) && - peep->no_of_rides == 0 && peep->guest_heading_to_ride_id == 0xFF) - { - - uint32 time_duration = gScenarioTicks - peep->time_in_park; - time_duration /= 2048; - - if (time_duration >= 5) - { - peep->PickRideToGoOn(); - - if (peep->guest_heading_to_ride_id == 0xFF) - { - peep->happiness_target = Math::Max(peep->happiness_target - 128, 0); - peep_leave_park(peep); - peep_update_hunger(peep); - goto loc_68F9F3; - } - } - } - - if ((scenario_rand() & 0xFFFF) <= ((peep->item_standard_flags & PEEP_ITEM_MAP) ? 8192U : 2184U)) - { - peep->PickRideToGoOn(); - } - - if ((uint32)(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 (peep->outside_of_park == 0 && (peep->state == PEEP_STATE_WALKING || peep->state == PEEP_STATE_SITTING)) - { - - uint8 num_thoughts = 0; - uint8 possible_thoughts[5] = { 0 }; - - if (peep->peep_flags & PEEP_FLAGS_LEAVING_PARK) - { - possible_thoughts[num_thoughts++] = PEEP_THOUGHT_TYPE_GO_HOME; - } - else - { - if (peep->energy <= 70 && peep->happiness < 128) - { - possible_thoughts[num_thoughts++] = PEEP_THOUGHT_TYPE_TIRED; - } - - if (peep->hunger <= 10 && !peep->HasFood()) - { - possible_thoughts[num_thoughts++] = PEEP_THOUGHT_TYPE_HUNGRY; - } - - if (peep->thirst <= 25 && !peep->HasFood()) - { - possible_thoughts[num_thoughts++] = PEEP_THOUGHT_TYPE_THIRSTY; - } - - if (peep->toilet >= 160) - { - possible_thoughts[num_thoughts++] = PEEP_THOUGHT_TYPE_BATHROOM; - } - - if (!(gParkFlags & PARK_FLAGS_NO_MONEY) && peep->cash_in_pocket <= MONEY(9, 00) && peep->happiness >= 105 && - peep->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) - { - uint8 chosen_thought = possible_thoughts[scenario_rand() % num_thoughts]; - - peep_insert_new_thought(peep, chosen_thought, PEEP_THOUGHT_ITEM_NONE); - - switch (chosen_thought) - { - case PEEP_THOUGHT_TYPE_HUNGRY: - peep_head_for_nearest_ride_with_flags(peep, RIDE_TYPE_FLAG_SELLS_FOOD); - break; - case PEEP_THOUGHT_TYPE_THIRSTY: - peep_head_for_nearest_ride_with_flags(peep, RIDE_TYPE_FLAG_SELLS_DRINKS); - break; - case PEEP_THOUGHT_TYPE_BATHROOM: - peep_head_for_nearest_ride_with_flags(peep, RIDE_TYPE_FLAG_IS_BATHROOM); - break; - case PEEP_THOUGHT_TYPE_RUNNING_OUT: - peep_head_for_nearest_ride_type(peep, RIDE_TYPE_CASH_MACHINE); - 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 (peep->nausea >= 140) - { - uint8 thought_type = PEEP_THOUGHT_TYPE_SICK; - if (peep->nausea >= 200) - { - thought_type = PEEP_THOUGHT_TYPE_VERY_SICK; - peep_head_for_nearest_ride_type(peep, RIDE_TYPE_FIRST_AID); - } - peep_insert_new_thought(peep, thought_type, PEEP_THOUGHT_ITEM_NONE); - } - } - - switch (peep->state) - { - case PEEP_STATE_WALKING: - case PEEP_STATE_LEAVING_PARK: - case PEEP_STATE_ENTERING_PARK: - peep_decide_whether_to_leave_park(peep); - peep_update_hunger(peep); - break; - - case PEEP_STATE_SITTING: - if (peep->energy_target <= 135) - peep->energy_target += 5; - - if (peep->thirst >= 5) - { - peep->thirst -= 4; - peep->toilet = Math::Min(255, peep->toilet + 3); - } - - if (peep->nausea_target >= 50) - peep->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(peep); - break; - - case PEEP_STATE_QUEUING: - if (peep->time_in_queue >= 2000) - { - /* Peep happiness is affected once the peep has been waiting - * too long in a queue. */ - rct_tile_element * tileElement = map_get_first_element_at(peep->next_x / 32, peep->next_y / 32); - bool found = false; - do - { - if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH) - continue; - if (tileElement->base_height != peep->next_z) - continue; - - // Check if the footpath has a queue line TV monitor on it - if (footpath_element_has_path_scenery(tileElement) && !footpath_element_path_scenery_is_ghost(tileElement)) - { - uint8 pathSceneryIndex = footpath_element_get_path_scenery_index(tileElement); - rct_scenery_entry * sceneryEntry = get_footpath_item_entry(pathSceneryIndex); - if (sceneryEntry->path_bit.flags & PATH_BIT_FLAG_IS_QUEUE_SCREEN) - { - found = true; - } - } - break; - } while (!tile_element_is_last_for_tile(tileElement++)); - - 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 (peep->happiness_target < 90) - peep->happiness_target = 90; - - if (peep->happiness_target < 165) - peep->happiness_target += 2; - } - else - { - /* Without a queue line TV monitor peeps waiting too long - * in a queue get less happy. */ - peep->happiness_target = Math::Max(peep->happiness_target - 4, 0); - } - } - peep_update_hunger(peep); - break; - case PEEP_STATE_ENTERING_RIDE: - if (peep->sub_state == 17 || peep->sub_state == 15) - { - peep_decide_whether_to_leave_park(peep); - } - peep_update_hunger(peep); - break; - } - - loc_68F9F3: - // Idle peep happiness tends towards 127 (50%). - if (peep->happiness_target >= 128) - peep->happiness_target--; - else - peep->happiness_target++; - - peep->nausea_target = Math::Max(peep->nausea_target - 2, 0); - - if (peep->energy <= 50) - { - peep->energy = Math::Max(peep->energy - 2, 0); - } - - if (peep->hunger < 10) - { - peep->hunger = Math::Max(peep->hunger - 1, 0); - } - - if (peep->thirst < 10) - { - peep->thirst = Math::Max(peep->thirst - 1, 0); - } - - if (peep->toilet >= 195) - { - peep->toilet--; - } - - if (peep->state == PEEP_STATE_WALKING && peep->nausea_target >= 128) - { - - if ((scenario_rand() & 0xFF) <= (uint8)((peep->nausea - 128) / 2)) - { - if (peep->action >= PEEP_ACTION_NONE_1) - { - peep->action = PEEP_ACTION_THROW_UP; - peep->action_frame = 0; - peep->action_sprite_image_offset = 0; - peep->UpdateCurrentActionSpriteType(); - peep->Invalidate(); - } - } - } - } - - // Remaining content is executed every call. - - // 68FA89 - if (peep->time_to_consume == 0 && peep->HasFood()) - { - peep->time_to_consume += 3; - } - - if (peep->time_to_consume != 0 && peep->state != PEEP_STATE_ON_RIDE) - { - - peep->time_to_consume = Math::Max(peep->time_to_consume - 3, 0); - - if (peep->HasDrink()) - { - peep->thirst = Math::Min(peep->thirst + 7, 255); - } - else - { - peep->hunger = Math::Min(peep->hunger + 7, 255); - peep->thirst = Math::Max(peep->thirst - 3, 0); - peep->toilet = Math::Min(peep->toilet + 2, 255); - } - - if (peep->time_to_consume == 0) - { - sint32 chosen_food = bitscanforward(peep->HasFoodStandardFlag()); - if (chosen_food != -1) - { - peep->item_standard_flags &= ~(1 << chosen_food); - - uint8 discard_container = peep_item_containers[chosen_food]; - if (discard_container != 0xFF) - { - peep->item_standard_flags |= (1 << discard_container); - } - - peep->window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY; - peep_update_sprite_type(peep); - } - else - { - chosen_food = bitscanforward(peep->HasFoodExtraFlag()); - if (chosen_food != -1) - { - peep->item_extra_flags &= ~(1 << chosen_food); - uint8 discard_container = peep_extra_item_containers[chosen_food]; - if (discard_container != 0xFF) - { - if (discard_container >= 32) - peep->item_extra_flags |= (1 << (discard_container - 32)); - else - peep->item_standard_flags |= (1 << discard_container); - } - - peep->window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY; - peep_update_sprite_type(peep); - } - } - } - } - - uint8 energy = peep->energy; - uint8 energy_target = peep->energy_target; - if (energy >= energy_target) - { - energy -= 2; - if (energy < energy_target) - energy = energy_target; + peep->Tick128UpdateStaff(); } else { - energy = Math::Min(PEEP_MAX_ENERGY_TARGET, energy + 4); - if (energy > energy_target) - energy = energy_target; - } - - if (energy < PEEP_MIN_ENERGY) - energy = PEEP_MIN_ENERGY; - - /* Previous code here suggested maximum energy is 128. */ - energy = Math::Min(static_cast(PEEP_MAX_ENERGY), energy); - - if (energy != peep->energy) - { - peep->energy = energy; - peep->window_invalidate_flags |= PEEP_INVALIDATE_PEEP_2; - } - - uint8 happiness = peep->happiness; - uint8 happiness_growth = peep->happiness_target; - if (happiness >= happiness_growth) - { - happiness = Math::Max(happiness - 4, 0); - if (happiness < happiness_growth) - happiness = happiness_growth; - } - else - { - happiness = Math::Min(255, happiness + 4); - if (happiness > happiness_growth) - happiness = happiness_growth; - } - - if (happiness != peep->happiness) - { - peep->happiness = happiness; - peep->window_invalidate_flags |= PEEP_INVALIDATE_PEEP_2; - } - - uint8 nausea = peep->nausea; - uint8 nausea_growth = peep->nausea_target; - if (nausea >= nausea_growth) - { - nausea = Math::Max(nausea - 4, 0); - if (nausea < nausea_growth) - nausea = nausea_growth; - } - else - { - nausea = Math::Min(255, nausea + 4); - if (nausea > nausea_growth) - nausea = nausea_growth; - } - - if (nausea != peep->nausea) - { - peep->nausea = nausea; - peep->window_invalidate_flags |= PEEP_INVALIDATE_PEEP_2; + peep->Tick128UpdateGuest(index); } } @@ -1442,104 +569,12 @@ void rct_peep::SwitchToSpecialSprite(uint8 special_sprite_id) UpdateCurrentActionSpriteType(); } -/** - * - * rct2: 0x0069A512 - */ -void rct_peep::RemoveFromRide() -{ - if (state == PEEP_STATE_QUEUING) - { - RemoveFromQueue(); - } - StateReset(); -} - void rct_peep::StateReset() { SetState(PEEP_STATE_1); SwitchToSpecialSprite(0); } -/** - * - * rct2: 0x69C308 - * Check if lost. - */ -void rct_peep::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 = Math::Max(happiness_target - 30, 0); -} - -/** - * - * rct2: 0x69C26B - * Check if cant find ride. - */ -void rct_peep::CheckCantFindRide() -{ - if (guest_heading_to_ride_id == 0xFF) - 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 = Math::Max(happiness_target - 30, 0); - } - - peep_is_lost_countdown--; - if (peep_is_lost_countdown != 0) - return; - - guest_heading_to_ride_id = 0xFF; - 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 rct_peep::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 = Math::Max(happiness_target - 30, 0); - } - - if (--peep_is_lost_countdown == 0) - peep_is_lost_countdown = 90; -} - /** rct2: 0x00981D7C, 0x00981D7E */ static constexpr const LocationXY16 word_981D7C[4] = { { -2, 0 }, { 0, 2 }, { 2, 0 }, { 0, -2 } }; @@ -1693,8 +728,8 @@ static void set_sprite_type(rct_peep * peep, uint8 type) peep->action = PEEP_ACTION_NONE_2; peep->peep_flags &= ~PEEP_FLAGS_SLOW_WALK; - assert(type < Util::CountOf(SpriteTypeToSlowWalkMap)); - if (SpriteTypeToSlowWalkMap[type]) + assert(type < Util::CountOf(gSpriteTypeToSlowWalkMap)); + if (gSpriteTypeToSlowWalkMap[type]) { peep->peep_flags |= PEEP_FLAGS_SLOW_WALK; } @@ -3393,81 +2428,6 @@ sint32 peep_is_mechanic(rct_peep * peep) peep->staff_type == STAFF_TYPE_MECHANIC); } -bool rct_peep::HasItem(sint32 peepItem) const -{ - if (peepItem < 32) - { - return item_standard_flags & (1u << peepItem); - } - else - { - return item_extra_flags & (1u << (peepItem - 32)); - } -} - -sint32 rct_peep::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); -} - -sint32 rct_peep::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); -} - -/** - * To simplify check of 0x36BA3E0 and 0x11FF78 - * returns false on no food. - */ -bool rct_peep::HasFood() const -{ - return HasFoodStandardFlag() || HasFoodExtraFlag(); -} - -bool rct_peep::HasDrinkStandardFlag() const -{ - return item_standard_flags & (PEEP_ITEM_DRINK | PEEP_ITEM_COFFEE | PEEP_ITEM_LEMONADE); -} - -bool rct_peep::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 rct_peep::HasDrink() const -{ - return HasDrinkStandardFlag() || HasDrinkExtraFlag(); -} - -sint32 rct_peep::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); -} - -sint32 rct_peep::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 rct_peep::HasEmptyContainer() const -{ - return HasEmptyContainerStandardFlag() || HasEmptyContainerExtraFlag(); -} - /** * * rct2: 0x699F5A @@ -4442,694 +3402,6 @@ sint32 rct_peep::PerformNextAction(uint8 & pathing_result, rct_tile_element * & return peep_return_to_centre_of_tile(this); } -// Used when no logging to an expend type required -void rct_peep::SpendMoney(money32 amount) -{ - money16 unused; - SpendMoney(unused, amount); -} - -/** - * - * rct2: 0x0069926C - * Expend type was previously an offset saved in 0x00F1AEC0 - */ -void rct_peep::SpendMoney(money16 & peep_expend_type, money32 amount) -{ - assert(!(gParkFlags & PARK_FLAGS_NO_MONEY)); - - cash_in_pocket = Math::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 rct_peep::SetHasRidden(sint32 rideIndex) -{ - rides_been_on[rideIndex / 8] |= 1 << (rideIndex % 8); - Ride * ride = get_ride(rideIndex); - SetHasRiddenRideType(ride->type); -} - -bool rct_peep::HasRidden(sint32 rideIndex) const -{ - return rides_been_on[rideIndex / 8] & (1 << (rideIndex % 8)); -} - -void rct_peep::SetHasRiddenRideType(sint32 rideType) -{ - ride_types_been_on[rideType / 8] |= 1 << (rideType % 8); -} - -bool rct_peep::HasRiddenRideType(sint32 rideType) const -{ - return ride_types_been_on[rideType / 8] & (1 << (rideType % 8)); -} - -/** - * Updates various peep stats upon entering a ride, as well as updating the - * ride's satisfaction value. - * rct2: 0x0069545B - */ -void rct_peep::OnEnterRide(uint8 rideIndex) -{ - Ride * ride = get_ride(rideIndex); - - // Calculate how satisfying the ride is for the peep. Can range from -140 to +105. - sint16 satisfaction = peep_calculate_ride_satisfaction(this, ride); - - // Update the satisfaction stat of the ride. - uint8 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(current_ride); - peep_update_favourite_ride(this, ride); - happiness_target = Math::Clamp(0, happiness_target + satisfaction, PEEP_MAX_HAPPINESS); - peep_update_ride_nausea_growth(this, ride); -} - -/** - * 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(rct_peep * peep, Ride * ride) -{ - peep->peep_flags &= ~PEEP_FLAGS_RIDE_SHOULD_BE_MARKED_AS_FAVOURITE; - uint8 peepRideRating = Math::Clamp(0, (ride->excitement / 4) + peep->happiness, 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 sint16 peep_calculate_ride_value_satisfaction(rct_peep * peep, Ride * ride) -{ - if (gParkFlags & PARK_FLAGS_NO_MONEY) - { - return -30; - } - - if (ride->value == 0xFFFF) - { - 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 sint16 peep_calculate_ride_intensity_nausea_satisfaction(rct_peep * peep, Ride * ride) -{ - if (!ride_has_ratings(ride)) - { - return 70; - } - - uint8 intensitySatisfaction = 3; - uint8 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 highestSatisfaction = Math::Max(intensitySatisfaction, nauseaSatisfaction); - uint8 lowestSatisfaction = Math::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; - } - } -} - -/** - * 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 sint16 peep_calculate_ride_satisfaction(rct_peep * peep, Ride * ride) -{ - sint16 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(peep->current_ride)) - satisfaction += 10; - - return satisfaction; -} - -/** - * 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(rct_peep * peep, Ride * ride) -{ - uint32 nauseaMultiplier = Math::Clamp(64, 256 - peep->happiness_target, 200); - uint32 nauseaGrowthRateChange = (ride->nausea * nauseaMultiplier) / 512; - nauseaGrowthRateChange *= Math::Max(static_cast(128), peep->hunger) / 64; - nauseaGrowthRateChange >>= (peep->nausea_tolerance & 3); - peep->nausea_target = (uint8)Math::Min(peep->nausea_target + nauseaGrowthRateChange, 255u); -} - -static bool peep_should_go_on_ride_again(rct_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 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(rct_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(rct_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: 0x0069576E - */ -void rct_peep::OnExitRide(uint8 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); - - rct_window * w = window_find_by_number(WC_PEEP, sprite_index); - if (w != nullptr) - { - window_event_invalidate_call(w); - widget_invalidate(w, WC_PEEP__WIDX_ACTION_LBL); - } - } - - 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); - - sint32 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; -} - -/** 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 rct_peep::DecideAndBuyItem(uint8 rideIndex, sint32 shopItem, money32 price) -{ - Ride * ride = get_ride(rideIndex); - 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)) - { - sint32 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" - uint8 thought_type = (shopItem >= 32 ? (PEEP_THOUGHT_TYPE_PHOTO2_MUCH + (shopItem - 32)) - : (PEEP_THOUGHT_TYPE_BALLOON_MUCH + shopItem)); - peep_insert_new_thought(this, thought_type, rideIndex); - return false; - } - } - else - { - itemValue -= price; - itemValue = Math::Max(8, itemValue); - - if (!(gParkFlags & PARK_FLAGS_NO_MONEY)) - { - if (itemValue >= (money32)(scenario_rand() & 0x07)) - { - // "This x is a really good value" - uint8 thought_item = (shopItem >= 32 ? (PEEP_THOUGHT_TYPE_PHOTO2 + (shopItem - 32)) - : (PEEP_THOUGHT_TYPE_BALLOON + shopItem)); - peep_insert_new_thought(this, thought_item, rideIndex); - } - } - - sint32 happinessGrowth = itemValue * 4; - happiness_target = Math::Min((happiness_target + happinessGrowth), PEEP_MAX_HAPPINESS); - happiness = Math::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 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_main[0]; - - if (shopItem == SHOP_ITEM_HAT) - hat_colour = ride->track_colour_main[0]; - - if (shopItem == SHOP_ITEM_BALLOON) - balloon_colour = ride->track_colour_main[0]; - - if (shopItem == SHOP_ITEM_UMBRELLA) - umbrella_colour = ride->track_colour_main[0]; - - if (shopItem == SHOP_ITEM_MAP) - peep_reset_pathfind_goal(this); - - uint16 consumptionTime = item_consumption_time[shopItem]; - time_to_consume = Math::Min((time_to_consume + consumptionTime), 255); - - if (shopItem == SHOP_ITEM_PHOTO) - photo1_ride_ref = rideIndex; - - if (shopItem == SHOP_ITEM_PHOTO2) - photo2_ride_ref = rideIndex; - - if (shopItem == SHOP_ITEM_PHOTO3) - photo3_ride_ref = rideIndex; - - if (shopItem == SHOP_ITEM_PHOTO4) - photo4_ride_ref = rideIndex; - - window_invalidate_flags |= PEEP_INVALIDATE_PEEP_INVENTORY; - peep_update_sprite_type(this); - if (peep_flags & PEEP_FLAGS_TRACKING) - { - set_format_arg(0, rct_string_id, name_string_idx); - set_format_arg(2, uint32, 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; -} - /** * * rct2: 0x0069A98C @@ -5314,258 +3586,6 @@ sint32 rct_peep::GetZOnSlope(sint32 tile_x, sint32 tile_y) return height + map_height_from_slope(tile_x, tile_y, next_var_29); } -/** - * - * rct2: 0x00695B70 - */ -static void peep_head_for_nearest_ride_type(rct_peep * peep, sint32 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 != 255) - { - ride = get_ride(peep->guest_heading_to_ride_id); - if (ride->type == rideType) - { - return; - } - } - - uint32 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 - sint32 i; - FOR_ALL_RIDES(i, ride) - { - if (ride->type == rideType) - { - rideConsideration[i >> 5] |= (1u << (i & 0x1F)); - } - } - } - else - { - // Take nearby rides into consideration - sint32 cx = floor2(peep->x, 32); - sint32 cy = floor2(peep->y, 32); - for (sint32 x = cx - 320; x <= cx + 320; x += 32) - { - for (sint32 y = cy - 320; y <= cy + 320; y += 32) - { - if (x >= 0 && y >= 0 && x < (256 * 32) && y < (256 * 32)) - { - rct_tile_element * tileElement = map_get_first_element_at(x >> 5, y >> 5); - do - { - if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK) - continue; - - sint32 rideIndex = track_element_get_ride_index(tileElement); - ride = get_ride(rideIndex); - if (ride->type == rideType) - { - rideConsideration[rideIndex >> 5] |= (1u << (rideIndex & 0x1F)); - } - } while (!tile_element_is_last_for_tile(tileElement++)); - } - } - } - } - - // Filter the considered rides - uint8 potentialRides[256]; - uint8 * nextPotentialRide = &potentialRides[0]; - sint32 numPotentialRides = 0; - for (sint32 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(i, 0, false, true)) - { - *nextPotentialRide++ = i; - numPotentialRides++; - } - } - } - - // Pick the closest ride - sint32 closestRideIndex = -1; - sint32 closestRideDistance = std::numeric_limits::max(); - for (sint32 i = 0; i < numPotentialRides; i++) - { - ride = get_ride(potentialRides[i]); - sint32 rideX = ride->station_starts[0].x * 32; - sint32 rideY = ride->station_starts[0].y * 32; - sint32 distance = abs(rideX - peep->x) + abs(rideY - peep->y); - if (distance < closestRideDistance) - { - closestRideIndex = potentialRides[i]; - closestRideDistance = distance; - } - } - if (closestRideIndex == -1) - 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); - widget_invalidate(w, WC_PEEP__WIDX_ACTION_LBL); - } - - peep->time_lost = 0; -} - -/** - * - * rct2: 0x006958D0 - */ -static void peep_head_for_nearest_ride_with_flags(rct_peep * peep, sint32 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 != 255) - { - 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 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 - sint32 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 - sint32 cx = floor2(peep->x, 32); - sint32 cy = floor2(peep->y, 32); - for (sint32 x = cx - 320; x <= cx + 320; x += 32) - { - for (sint32 y = cy - 320; y <= cy + 320; y += 32) - { - if (x >= 0 && y >= 0 && x < (256 * 32) && y < (256 * 32)) - { - rct_tile_element * tileElement = map_get_first_element_at(x >> 5, y >> 5); - do - { - if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK) - continue; - - sint32 rideIndex = track_element_get_ride_index(tileElement); - ride = get_ride(rideIndex); - if (ride_type_has_flag(ride->type, rideTypeFlags)) - { - rideConsideration[rideIndex >> 5] |= (1u << (rideIndex & 0x1F)); - } - } while (!tile_element_is_last_for_tile(tileElement++)); - } - } - } - } - - // Filter the considered rides - uint8 potentialRides[256]; - uint8 * nextPotentialRide = &potentialRides[0]; - sint32 numPotentialRides = 0; - for (sint32 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(i, 0, false, true)) - { - *nextPotentialRide++ = i; - numPotentialRides++; - } - } - } - - // Pick the closest ride - sint32 closestRideIndex = -1; - sint32 closestRideDistance = std::numeric_limits::max(); - for (sint32 i = 0; i < numPotentialRides; i++) - { - ride = get_ride(potentialRides[i]); - sint32 rideX = ride->station_starts[0].x * 32; - sint32 rideY = ride->station_starts[0].y * 32; - sint32 distance = abs(rideX - peep->x) + abs(rideY - peep->y); - if (distance < closestRideDistance) - { - closestRideIndex = potentialRides[i]; - closestRideDistance = distance; - } - } - if (closestRideIndex == -1) - 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: 0x0069C483 diff --git a/src/openrct2/peep/Peep.h b/src/openrct2/peep/Peep.h index df2238c92a..34a03fa061 100644 --- a/src/openrct2/peep/Peep.h +++ b/src/openrct2/peep/Peep.h @@ -697,6 +697,7 @@ public: // Peep void StateReset(); void MoveTo(sint16 destX, sint16 destY, sint16 destZ); public: // Guest + void Tick128UpdateGuest(sint32 index); void RemoveFromQueue(); bool HasItem(sint32 peepItem) const; bool HasFood() const; @@ -706,6 +707,8 @@ public: // Guest void OnExitRide(uint8 rideIndex); void RemoveFromRide(); bool HeadingForRideOrParkExit() const; +public: // Staff + void Tick128UpdateStaff(); private: // Peep update void UpdateFalling(); @@ -865,6 +868,7 @@ enum // rct2: 0x00982708 extern rct_peep_animation_entry g_peep_animation_entries[PEEP_SPRITE_TYPE_COUNT]; +extern const bool gSpriteTypeToSlowWalkMap[]; extern uint8 gGuestChangeModifier; extern uint16 gNumGuestsInPark; diff --git a/src/openrct2/peep/Staff.cpp b/src/openrct2/peep/Staff.cpp index 3dcb47ec1b..c73635bb83 100644 --- a/src/openrct2/peep/Staff.cpp +++ b/src/openrct2/peep/Staff.cpp @@ -2325,6 +2325,34 @@ static sint32 peep_update_patrolling_find_sweeping(rct_peep * peep) return 0; } +void rct_peep::Tick128UpdateStaff() +{ + if (staff_type != STAFF_TYPE_SECURITY) + return; + + uint8 newSpriteType = PEEP_SPRITE_TYPE_SECURITY_ALT; + if (state != PEEP_STATE_PATROLLING) + newSpriteType = PEEP_SPRITE_TYPE_SECURITY; + + if (sprite_type == newSpriteType) + return; + + sprite_type = newSpriteType; + 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; + if (gSpriteTypeToSlowWalkMap[newSpriteType]) + { + peep_flags |= PEEP_FLAGS_SLOW_WALK; + } + + action_sprite_type = 0xFF; + UpdateCurrentActionSpriteType(); +} + /** * * rct2: 0x006BF1FD