diff --git a/projects/openrct2.vcxproj b/projects/openrct2.vcxproj index a7a7e5578d..baa3d25704 100644 --- a/projects/openrct2.vcxproj +++ b/projects/openrct2.vcxproj @@ -89,6 +89,7 @@ + @@ -190,6 +191,7 @@ + diff --git a/projects/openrct2.vcxproj.filters b/projects/openrct2.vcxproj.filters index c25f074b1b..3cf9846b55 100644 --- a/projects/openrct2.vcxproj.filters +++ b/projects/openrct2.vcxproj.filters @@ -381,7 +381,6 @@ - Source\Drawing @@ -434,6 +433,10 @@ Source\Windows + + + Source\Ride + @@ -631,5 +634,8 @@ Source\World + + Source\Ride + \ No newline at end of file diff --git a/src/addresses.h b/src/addresses.h index 5abb64b0b9..fcd74c5c49 100644 --- a/src/addresses.h +++ b/src/addresses.h @@ -493,7 +493,7 @@ static void RCT2_CALLPROC_EBPSAFE(int address) /* Returns the flags register * *Flags register is as follows: - *0bSZ0A_0P0C_0000_00000 + *0bSZ0A_0P0C_0000_0000 *S = Signed flag *Z = Zero flag *C = Carry flag diff --git a/src/audio/audio.c b/src/audio/audio.c index 4a0f5fd2de..ea9d9fa39f 100644 --- a/src/audio/audio.c +++ b/src/audio/audio.c @@ -1836,35 +1836,6 @@ void stop_vehicle_sounds() } } -/** -* Update zoom based volume attenuation for ride music and clear music list -* rct2: 0x006BC348 -*/ -void sub_6BC348() -{ - RCT2_GLOBAL(0x009AF42C, rct_music_info*) = &RCT2_GLOBAL(0x009AF430, rct_music_info); - RCT2_GLOBAL(0x00F438A4, rct_viewport*) = (rct_viewport*)-1; - rct_window* window = RCT2_GLOBAL(RCT2_ADDRESS_NEW_WINDOW_PTR, rct_window*); - while (1) { - window--; - if (window < RCT2_ADDRESS(RCT2_ADDRESS_WINDOW_LIST, rct_window)) { - break; - } - if (window->viewport && window->viewport->flags & VIEWPORT_FLAG_SOUND_ON) { - RCT2_GLOBAL(0x00F438A4, rct_viewport*) = window->viewport; - RCT2_GLOBAL(0x00F438A8, rct_window*) = window; - RCT2_GLOBAL(RCT2_ADDRESS_VOLUME_ADJUST_ZOOM, uint8) = 0; - if (window->viewport->zoom) { - RCT2_GLOBAL(RCT2_ADDRESS_VOLUME_ADJUST_ZOOM, uint8) = 30; - if (window->viewport->zoom != 1) { - RCT2_GLOBAL(RCT2_ADDRESS_VOLUME_ADJUST_ZOOM, uint8) = 60; - } - } - return; - } - } -} - /** * * rct2: 0x006BC3AC @@ -1874,17 +1845,32 @@ void sub_6BC348() * @param x (ax) * @param y (cx) * @param z (dx) - * @param bx - * @param ebp - * @param di - * @returns ebp register + * @param sampleRate (di) + * @param rideIndex (bl) + * @param position (ebp) + * @param tuneId (bh) + * @returns new position (ebp) */ -int sub_6BC3AC(sint16 x, sint16 y, sint16 z, uint16 bx, uint32 ebp, uint16 di) +int sub_6BC3AC(sint16 x, sint16 y, sint16 z, uint8 rideIndex, uint16 sampleRate, uint32 position, uint8 *tuneId) { - uint8 bl = LOBYTE(bx); - uint8 bh = HIBYTE(bx); + { + int a_eax, a_ebx, a_ecx, a_edx, a_esi, a_edi, a_ebp; + + a_eax = x; + a_ebx = (*tuneId << 8) | rideIndex; + a_ecx = y; + a_edx = z; + a_edi = sampleRate; + a_ebp = position; + RCT2_CALLFUNC_X(0x006BC3AC, &a_eax, &a_ebx, &a_ecx, &a_edx, &a_esi, &a_edi, &a_ebp); + + *tuneId = (a_ebx >> 8) & 0xFF; + return a_ebp; + } + + // TODO fix / bh needs returning too! if(!(RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) & SCREEN_FLAGS_SCENARIO_EDITOR) && !RCT2_GLOBAL(0x009AF59C, uint8) && RCT2_GLOBAL(0x00F438A4, rct_viewport*) != (rct_viewport*)-1) { - RCT2_GLOBAL(0x009AF47C, uint16) = di; + RCT2_GLOBAL(0x009AF47C, uint16) = sampleRate; sint16 v11; sint16 v12; switch (RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_ROTATION, uint32)) { @@ -1988,12 +1974,12 @@ int sub_6BC3AC(sint16 x, sint16 y, sint16 z, uint16 bx, uint32 ebp, uint16 di) rct_music_info2* music_info2 = &RCT2_GLOBAL(0x009AF46C, rct_music_info2); int channel = 0; uint32 a1; - while (music_info2->id != bl && music_info2->var_1 != bh) { + while (music_info2->id != rideIndex && music_info2->var_1 != *tuneId) { music_info2++; channel++; if (channel >= 2) { - rct_music_info3* music_info3 = &RCT2_GLOBAL(0x009AF1C8, rct_music_info3*)[bh]; - a1 = ebp + music_info3->var_4; + rct_music_info3* music_info3 = &RCT2_GLOBAL(0x009AF1C8, rct_music_info3*)[*tuneId]; + a1 = position + music_info3->var_4; goto label51; } } @@ -2007,11 +1993,11 @@ int sub_6BC3AC(sint16 x, sint16 y, sint16 z, uint16 bx, uint32 ebp, uint16 di) a1 = sub_401B46(channel); RCT2_GLOBAL(0x014241BC, uint32) = 0; label51: - if (a1 < RCT2_GLOBAL(0x009AF1C8, rct_music_info3*)[bh].var_0) { + if (a1 < RCT2_GLOBAL(0x009AF1C8, rct_music_info3*)[*tuneId].var_0) { rct_music_info* music_info = RCT2_GLOBAL(0x009AF42C, rct_music_info*); if (music_info < (rct_music_info*)0x009AF46C/*music_info list end*/) { - music_info->id = bl; - music_info->var_1 = bh; + music_info->id = rideIndex; + music_info->var_1 = *tuneId; music_info->offset = a1; music_info->volume = v32; music_info->pan = panx; @@ -2022,16 +2008,16 @@ int sub_6BC3AC(sint16 x, sint16 y, sint16 z, uint16 bx, uint32 ebp, uint16 di) } else { uint32 eax; label58: - eax = ebp; - ebp = bh; - rct_music_info3* music_info3 = &RCT2_GLOBAL(0x009AF1C8, rct_music_info3*)[bh]; + eax = position; + position = *tuneId; + rct_music_info3* music_info3 = &RCT2_GLOBAL(0x009AF1C8, rct_music_info3*)[*tuneId]; eax += music_info3->var_4; if (eax < music_info3->var_0) { - ebp = eax; + position = eax; } } } - return ebp; + return position; } /** diff --git a/src/audio/audio.h b/src/audio/audio.h index 44e52fbaf6..cc91205edc 100644 --- a/src/audio/audio.h +++ b/src/audio/audio.h @@ -213,8 +213,8 @@ void audio_close(); void pause_sounds(); void unpause_sounds(); void stop_vehicle_sounds(); -void sub_6BC348(); void sub_6BC6D8(); +int sub_6BC3AC(sint16 x, sint16 y, sint16 z, uint8 rideIndex, uint16 sampleRate, uint32 position, uint8 *tuneId); // 0x009AF59C probably does the same job // once it's confirmed and calls in pause_sounds() are reversed, it can be used instead of this diff --git a/src/interface/window.c b/src/interface/window.c index 53f2602d7c..6fcfe9fbb0 100644 --- a/src/interface/window.c +++ b/src/interface/window.c @@ -1792,4 +1792,39 @@ void sub_6EA73F() window_invalidate_pressed_image_buttons(w); RCT2_CALLPROC_X(w->event_handlers[WE_RESIZE], 0, 0, 0, 0, (int)w, 0, 0); } +} + +/** + * Update zoom based volume attenuation for ride music and clear music list. + * rct2: 0x006BC348 + */ +void window_update_viewport_ride_music() +{ + rct_viewport *viewport; + rct_window *w; + + RCT2_GLOBAL(0x009AF42C, rct_music_info*) = (rct_music_info*)0x009AF430; + RCT2_GLOBAL(0x00F438A4, rct_viewport*) = (rct_viewport*)-1; + + for (w = RCT2_LAST_WINDOW; w >= g_window_list; w--) { + viewport = w->viewport; + if (viewport == NULL || !(viewport->flags & VIEWPORT_FLAG_SOUND_ON)) + continue; + + RCT2_GLOBAL(0x00F438A4, rct_viewport*) = viewport; + RCT2_GLOBAL(0x00F438A8, rct_window*) = w; + + switch (viewport->zoom) { + case 0: + RCT2_GLOBAL(RCT2_ADDRESS_VOLUME_ADJUST_ZOOM, uint8) = 0; + break; + case 1: + RCT2_GLOBAL(RCT2_ADDRESS_VOLUME_ADJUST_ZOOM, uint8) = 30; + break; + default: + RCT2_GLOBAL(RCT2_ADDRESS_VOLUME_ADJUST_ZOOM, uint8) = 60; + break; + } + break; + } } \ No newline at end of file diff --git a/src/interface/window.h b/src/interface/window.h index 27ab7d56b5..2e504e8aa2 100644 --- a/src/interface/window.h +++ b/src/interface/window.h @@ -463,6 +463,8 @@ void tool_cancel(); void window_close_construction_windows(); +void window_update_viewport_ride_music(); + // Open window functions void window_main_open(); void window_resize_gui(int width, int height); diff --git a/src/peep/peep.c b/src/peep/peep.c index 7412b278f5..59c60138cc 100644 --- a/src/peep/peep.c +++ b/src/peep/peep.c @@ -558,11 +558,11 @@ void get_arguments_from_action(rct_peep* peep, uint32 *argument_1, uint32* argum *argument_2 = 0; break; case PEEP_STATE_ANSWERING: - if (peep->pad_2C == 0){ + if (peep->var_2C == 0){ *argument_1 = STR_WALKING; *argument_2 = 0; } - else if (peep->pad_2C == 1){ + else if (peep->var_2C == 1){ *argument_1 = STR_ANSWERING_RADIO_CALL; *argument_2 = 0; } diff --git a/src/peep/peep.h b/src/peep/peep.h index e9e665e9bb..24f2cac38c 100644 --- a/src/peep/peep.h +++ b/src/peep/peep.h @@ -304,7 +304,7 @@ typedef struct { typedef struct { uint8 sprite_identifier; // 0x00 - uint8 pad_01; + uint8 var_01; uint16 var_02; // 0x02 uint16 next; // 0x04 uint16 previous; // 0x06 @@ -329,7 +329,7 @@ typedef struct { uint16 next_z; // 0x28 uint8 var_2A; uint8 state; // 0x2B - uint8 pad_2C; + uint8 var_2C; uint8 sprite_type; // 0x2D uint8 type; // 0x2E union{ @@ -363,7 +363,7 @@ typedef struct { uint8 photo4_ride_ref; // 0x5E uint8 pad_5F[0x09]; // 0x5C uint8 current_ride; // 0x68 - uint8 pad_69; + uint8 current_ride_station; // 0x69 uint8 current_train; // 0x6A uint8 current_car; // 0x6B uint8 current_seat; // 0x6C diff --git a/src/peep/staff.c b/src/peep/staff.c index 4bbcf64275..bd176a3781 100644 --- a/src/peep/staff.c +++ b/src/peep/staff.c @@ -290,3 +290,34 @@ void sub_6C0C3F() } } } + +int staff_is_location_in_patrol_area(rct_peep *peep, int x, int y) +{ + // Patrol quads are stored in a bit map (8 patrol quads per byte) + // Each patrol quad is 4x4 + // Therefore there are in total 64 x 64 patrol quads in the 256 x 256 map + int patrolOffset = peep->staff_id * (64 * 64 / 8); + int patrolIndex = ((x & 0x1F80) >> 7) | ((y & 0x1F80) >> 1); + int mask = 1 << (patrolIndex & 0x1F); + int base = patrolIndex >> 5; + + uint32 *patrolBits = (uint32*)(0x013B0E72 + patrolOffset + (base * 4)); + return (*patrolBits & mask) != 0; +} + +/** + * + * rct2: 0x006C0905 + */ +int mechanic_is_location_in_patrol(rct_peep *mechanic, int x, int y) +{ + // Check if location is in the park + if (!sub_664F72(x, y, mechanic->z)) + return 0; + + // Check if mechanic has patrol area + if (!(RCT2_ADDRESS(RCT2_ADDRESS_STAFF_MODE_ARRAY, uint8)[mechanic->staff_id] & 2)) + return 1; + + return staff_is_location_in_patrol_area(mechanic, x, y); +} \ No newline at end of file diff --git a/src/peep/staff.h b/src/peep/staff.h index 72e9f0c965..758159489c 100644 --- a/src/peep/staff.h +++ b/src/peep/staff.h @@ -22,6 +22,7 @@ #define _STAFF_H_ #include "../common.h" +#include "peep.h" #define STAFF_MAX_COUNT 0xC8 #define STAFF_TYPE_COUNT 0x04 @@ -45,5 +46,6 @@ void game_command_hire_new_staff_member(int* eax, int* ebx, int* ecx, int* edx, void update_staff_colour(uint8 staff_type, uint16 color); uint16 hire_new_staff_member(uint8 staff_type); void sub_6C0C3F(); +int mechanic_is_location_in_patrol(rct_peep *mechanic, int x, int y); #endif \ No newline at end of file diff --git a/src/ride/ride.c b/src/ride/ride.c index 9eaca0a549..62287777d0 100644 --- a/src/ride/ride.c +++ b/src/ride/ride.c @@ -20,17 +20,21 @@ #include #include "../addresses.h" +#include "../audio/audio.h" #include "../game.h" #include "../interface/window.h" +#include "../localisation/date.h" #include "../localisation/localisation.h" #include "../management/news_item.h" #include "../peep/peep.h" #include "../peep/staff.h" #include "../scenario.h" +#include "../util/util.h" #include "../world/map.h" #include "../world/sprite.h" #include "ride.h" #include "ride_data.h" +#include "station.h" #pragma region Ride classification table @@ -103,9 +107,47 @@ const uint8 gRideClassifications[255] = { #pragma endregion +static const int RideInspectionInterval[] = { + 10, 20, 30, 45, 60, 120, 0, 0 +}; + rct_ride_type **gRideTypeList = RCT2_ADDRESS(0x009ACFA4, rct_ride_type*); rct_ride* g_ride_list = RCT2_ADDRESS(RCT2_ADDRESS_RIDE_LIST, rct_ride); +// Static function declarations +rct_peep *find_closest_mechanic(int x, int y, int forInspection); +static void ride_breakdown_status_update(int rideIndex); +static void ride_breakdown_update(int rideIndex); +static void ride_call_mechanic(int rideIndex); +static void ride_chairlift_update(rct_ride *ride); +static void ride_entrance_exit_connected(rct_ride* ride, int ride_idx); +static int ride_get_new_breakdown_problem(rct_ride *ride); +static void ride_inspection_update(rct_ride *ride); +static void ride_mechanic_status_update(int rideIndex, int mechanicStatus); +static void ride_music_update(int rideIndex); +static void ride_prepare_breakdown(int rideIndex, int breakdownReason); +static void ride_shop_connected(rct_ride* ride, int ride_idx); +static void ride_spiral_slide_update(rct_ride *ride); +static void ride_update(int rideIndex); + +rct_ride_type *ride_get_entry(rct_ride *ride) +{ + return GET_RIDE_ENTRY(ride->subtype); +} + +uint8 *get_ride_entry_indices_for_ride_type(uint8 rideType) +{ + uint8 *typeToRideEntryIndexMap = (uint8*)0x009E32F8; + uint8 *entryIndexList = typeToRideEntryIndexMap; + while (rideType > 0) { + do { + entryIndexList++; + } while (*(entryIndexList - 1) != 255); + rideType--; + } + return entryIndexList; +} + int ride_get_count() { rct_ride *ride; @@ -135,44 +177,6 @@ int ride_get_max_queue_time(rct_ride *ride) return queueTime; } -/** - * - * rct2: 0x006ACA89 - */ -void ride_init_all() -{ - int i; - rct_ride *ride; - rct_ride_measurement *ride_measurement; - - for (i = 0; i < MAX_RIDES; i++) { - ride = &g_ride_list[i]; - ride->type = RIDE_TYPE_NULL; - } - - RCT2_GLOBAL(0x0138B590, sint8) = 0; - RCT2_GLOBAL(0x0138B591, sint8) = 0; - - for (i = 0; i < MAX_RIDE_MEASUREMENTS; i++) { - ride_measurement = GET_RIDE_MEASUREMENT(i); - ride_measurement->ride_index = 255; - } -} - -/** -* -* rct2: 0x006B7A38 -*/ -void reset_all_ride_build_dates() { - int i; - rct_ride *ride; - FOR_ALL_RIDES(i, ride) { - //mov ax, current_month_year - //sub [esi + 180h], ax - ride->build_date -= RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_MONTH_YEAR, uint16); - } -} - /** * rct2: 0x006AC916 */ @@ -201,157 +205,51 @@ void ride_update_favourited_stat() window_invalidate_by_class(WC_RIDE_LIST); } +money32 get_shop_item_cost(int shopItem) +{ + return shopItem < 32 ? + RCT2_GLOBAL(0x00982164 + (shopItem * 8), uint16) : + RCT2_GLOBAL(0x00982144 + (shopItem * 8), uint16); +} + /** * - * rct2: 0x006ABE4C + * rct2: 0x006AC3AB */ -void ride_update_all() +money32 ride_calculate_income_per_hour(rct_ride *ride) { - RCT2_CALLPROC_EBPSAFE(0x006ABE4C); -} + rct_ride_type *entry; + money32 incomePerHour, priceMinusCost; + int shopItem; -/** - * rct2: 0x006B7C59 - * @return 1 if the coordinate is reachable or has no entrance, 0 otherwise - */ -int ride_entrance_exit_is_reachable(uint16 coordinate, rct_ride* ride, int index) { - int x = ((coordinate >> 8) & 0xFF) << 5, // cx - y = (coordinate & 0xFF) << 5; // ax - uint8 station_height = ride->station_heights[index]; - int tile_idx = ((x << 8) | y) >> 5; - rct_map_element* tile = RCT2_ADDRESS(RCT2_ADDRESS_TILE_MAP_ELEMENT_POINTERS, rct_map_element*)[tile_idx]; + entry = GET_RIDE_ENTRY(ride->subtype); + incomePerHour = + ride->var_124 + + ride->var_126 + + ride->var_128 + + ride->var_12A + + ride->var_12C + + ride->var_12E + + ride->age + + ride->running_cost + + ride->var_134 + + ride->var_136; + incomePerHour *= 12; + priceMinusCost = ride->price; - while(1) { - uint8 element_type = tile->type & MAP_ELEMENT_TYPE_MASK; - if (element_type == MAP_ELEMENT_TYPE_ENTRANCE && station_height == tile->base_height) { - break; - } else if (tile->flags & MAP_ELEMENT_FLAG_LAST_TILE) { - return 1; - } - tile++; + shopItem = entry->shop_item; + if (shopItem != 255) { + priceMinusCost -= get_shop_item_cost(shopItem); + + shopItem = entry->shop_item_secondary; + if (shopItem != 255) { + priceMinusCost -= get_shop_item_cost(shopItem); + priceMinusCost /= 2; + } } - uint8 face_direction = tile->type & 3; - y -= RCT2_ADDRESS(0x00993CCC, sint16)[face_direction * 2]; - x -= RCT2_ADDRESS(0x00993CCE, sint16)[face_direction * 2]; - tile_idx = ((x << 8) | y) >> 5; - - return map_coord_is_connected(tile_idx, station_height, face_direction); -} - - -void ride_entrance_exit_connected(rct_ride* ride, int ride_idx) -{ - for (int i = 0; i < 4; ++i) { - uint16 station_start = ride->station_starts[i], - entrance = ride->entrances[i], - exit = ride->exits[i]; - - if (station_start == -1 ) - continue; - if (entrance != -1 && !ride_entrance_exit_is_reachable(entrance, ride, i)) { - // name of ride is parameter of the format string - RCT2_GLOBAL(0x013CE952, uint16) = ride->name; - RCT2_GLOBAL(0x013CE954, uint32) = ride->name_arguments; - news_item_add_to_queue(1, STR_ENTRANCE_NOT_CONNECTED, ride_idx); - ride->connected_message_throttle = 3; - } - - if (exit != -1 && !ride_entrance_exit_is_reachable(exit, ride, i)) { - // name of ride is parameter of the format string - RCT2_GLOBAL(0x013CE952, uint16) = ride->name; - RCT2_GLOBAL(0x013CE954, uint32) = ride->name_arguments; - news_item_add_to_queue(1, STR_EXIT_NOT_CONNECTED, ride_idx); - ride->connected_message_throttle = 3; - } - - } -} - - -void ride_shop_connected(rct_ride* ride, int ride_idx) -{ - rct_ride* ride_back = ride; - uint16 coordinate = ride->station_starts[0]; - if (coordinate == 0xFFFF) - return; - - int x = ((coordinate >> 8) & 0xFF) << 5, // cx - y = (coordinate & 0xFF) << 5; // ax - - rct_map_element* tile = RCT2_ADDRESS(RCT2_ADDRESS_TILE_MAP_ELEMENT_POINTERS, rct_map_element*)[coordinate]; - - for (; ; tile++){ - uint8 element_type = tile->type & MAP_ELEMENT_TYPE_MASK; - if(element_type == MAP_ELEMENT_TYPE_TRACK && tile->properties.track.ride_index == ride_idx) - break; - if(tile->flags & MAP_ELEMENT_FLAG_LAST_TILE) - return; - } - - uint16 entrance_directions = 0; - uint8 track_type = tile->properties.track.type; - ride = &g_ride_list[tile->properties.track.ride_index]; - if (RCT2_GLOBAL(RCT2_ADDRESS_RIDE_FLAGS + ride->type * 8, uint32) & 0x80000) { - entrance_directions = RCT2_ADDRESS(0x0099CA64, uint8)[track_type * 16]; - } else { - entrance_directions = RCT2_ADDRESS(0x0099BA64, uint8)[track_type * 16]; - } - - - uint8 tile_direction = tile->type & MAP_ELEMENT_DIRECTION_MASK; - entrance_directions <<= tile_direction; - entrance_directions = ((entrance_directions >> 12) | entrance_directions) & 0xF; - - // now each bit in entrance_directions stands for an entrance direction to check - if (entrance_directions == 0) - return; - - for (int count = 0; entrance_directions != 0; ++count) { - if (!(entrance_directions & 1)) { - entrance_directions >>= 1; - continue; - } - entrance_directions >>= 1; - - uint8 face_direction = count ^ 2; // flip direction north<->south, east<->west - int y2 = y - RCT2_ADDRESS(0x00993CCC, sint16)[face_direction * 2]; - int x2 = x - RCT2_ADDRESS(0x00993CCE, sint16)[face_direction * 2]; - int tile_idx = ((x2 << 8) | y2) >> 5; - - if (map_coord_is_connected(tile_idx, tile->base_height, face_direction)) - return; - } - - // name of ride is parameter of the format string - RCT2_GLOBAL(0x013CE952, uint16) = ride->name; - RCT2_GLOBAL(0x013CE954, uint32) = ride->name_arguments; - news_item_add_to_queue(1, STR_ENTRANCE_NOT_CONNECTED, ride_idx); - - ride->connected_message_throttle = 3; -} - - - -/** - * rct2: 0x006B7A5E - **/ -void ride_check_all_reachable() -{ - rct_ride *ride; - int i; - - FOR_ALL_RIDES(i, ride) { - if (ride->connected_message_throttle != 0) - ride->connected_message_throttle--; - if (ride->status != RIDE_STATUS_OPEN || ride->connected_message_throttle != 0) - continue; - - if (RCT2_GLOBAL(RCT2_ADDRESS_RIDE_FLAGS + ride->type * 8, uint32) & 0x20000) - ride_shop_connected(ride, i); - else - ride_entrance_exit_connected(ride, i); - } + incomePerHour *= priceMinusCost; + return incomePerHour; } /** @@ -432,6 +330,124 @@ rct_map_element *ride_find_track_gap(rct_map_element *startTrackElement, int *ou return (rct_map_element*)esi; } +/** + * + * rct2: 0x006AF561 + */ +void ride_get_status(int rideIndex, int *formatSecondary, int *argument) +{ + rct_ride *ride = &g_ride_list[rideIndex]; + + if (ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED) { + *formatSecondary = STR_CRASHED; + return; + } + if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) { + *formatSecondary = STR_BROKEN_DOWN; + return; + } + if (ride->status == RIDE_STATUS_CLOSED) { + *formatSecondary = STR_CLOSED; + return; + } + if (ride->status == RIDE_STATUS_TESTING) { + *formatSecondary = STR_TEST_RUN; + return; + } + rct_peep *peep = GET_PEEP(ride->race_winner); + if (ride->mode == RIDE_MODE_RACE && !(ride->lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING) && ride->race_winner != 0xFFFF && peep->sprite_identifier == SPRITE_IDENTIFIER_PEEP) { + if (peep->name_string_idx == STR_GUEST) { + *argument = peep->id; + *formatSecondary = STR_RACE_WON_BY_GUEST; + } else { + *argument = peep->name_string_idx; + *formatSecondary = STR_RACE_WON_BY; + } + } else { + if (!(RCT2_GLOBAL(RCT2_ADDRESS_RIDE_FLAGS + ride->type * 8, uint32) & 0x20000)) { + *argument = ride->num_riders; + *formatSecondary = STR_PERSON_ON_RIDE; + if(*argument != 1) + *formatSecondary = STR_PEOPLE_ON_RIDE; + + } else { + *formatSecondary = STR_OPEN; + } + } +} + +int ride_get_total_length(rct_ride *ride) +{ + int i, totalLength = 0; + for (i = 0; i < ride->num_stations; i++) + totalLength += ride->length[i]; + return totalLength; +} + +int ride_can_have_multiple_circuits(rct_ride *ride) +{ + if (!(RCT2_GLOBAL(0x0097D4F2 + (ride->type * 8), uint16) & 0x200)) + return 0; + + // Only allow circuit or launch modes + if ( + ride->mode != RIDE_MODE_CONTINUOUS_CIRCUIT && + ride->mode != RIDE_MODE_REVERSE_INCLINE_LAUNCHED_SHUTTLE && + ride->mode != RIDE_MODE_POWERED_LAUNCH + ) { + return 0; + } + + // Must have no more than one vehicle and one station + if (ride->num_vehicles > 1 || ride->num_stations > 1) + return 0; + + return 1; +} + +#pragma region Initialisation functions + +/** + * + * rct2: 0x006ACA89 + */ +void ride_init_all() +{ + int i; + rct_ride *ride; + rct_ride_measurement *ride_measurement; + + for (i = 0; i < MAX_RIDES; i++) { + ride = &g_ride_list[i]; + ride->type = RIDE_TYPE_NULL; + } + + RCT2_GLOBAL(0x0138B590, sint8) = 0; + RCT2_GLOBAL(0x0138B591, sint8) = 0; + + for (i = 0; i < MAX_RIDE_MEASUREMENTS; i++) { + ride_measurement = GET_RIDE_MEASUREMENT(i); + ride_measurement->ride_index = 255; + } +} + +/** +* +* rct2: 0x006B7A38 +*/ +void reset_all_ride_build_dates() +{ + int i; + rct_ride *ride; + + FOR_ALL_RIDES(i, ride) + ride->build_date -= RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_MONTH_YEAR, uint16); +} + +#pragma endregion + +#pragma region Construction + int ride_create_ride(ride_list_item listItem) { int eax, ebx, ecx, edx, esi, edi, ebp; @@ -482,50 +498,638 @@ int ride_try_construct(rct_map_element *trackMapElement) return 1; } +#pragma endregion + +#pragma region Update functions + /** - * - * rct2: 0x006AF561 + * + * rct2: 0x006ABE4C */ -void ride_get_status(int rideIndex, int *formatSecondary, int *argument) +void ride_update_all() { - rct_ride *ride = &g_ride_list[rideIndex]; + rct_s6_info *s6Info = (rct_s6_info*)0x0141F570; + rct_ride *ride; + int i; - if (ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED) { - *formatSecondary = STR_CRASHED; + // Remove all rides if certain flags are set (possible scenario editor?) + int *esi = (int*)0x9DCE9E; + if (esi[0x1BCA] & 2) { + if (s6Info->var_000 <= 2) + FOR_ALL_RIDES(i, ride) + ride->type = RIDE_TYPE_NULL; return; } + + window_update_viewport_ride_music(); + + // Update rides + FOR_ALL_RIDES(i, ride) + ride_update(i); + + sub_6BC6D8(); +} + +/** + * + * rct2: 0x006ABE73 + */ +static void ride_update(int rideIndex) +{ + int i; + rct_ride *ride = GET_RIDE(rideIndex); + + if (ride->var_1CA != 0) + ride->var_1CA--; + + ride_music_update(rideIndex); + + // Update stations + if (ride->type != RIDE_TYPE_MAZE) + for (i = 0; i < 4; i++) + ride_update_station(ride, i); + + // Update financial statistics + ride->var_122++; + if (ride->var_122 >= 960) { + ride->var_122 = 0; + + ride->var_136 = ride->var_134; + ride->var_134 = ride->running_cost; + ride->running_cost = ride->age; + ride->age = ride->var_12E; + ride->var_12E = ride->var_12C; + ride->var_12C = ride->var_12A; + ride->var_12A = ride->var_128; + ride->var_128 = ride->var_126; + ride->var_126 = ride->var_124; + ride->var_124 = ride->var_120; + ride->var_120 = 0; + ride->var_14D |= 1; + + ride->income_per_hour = ride_calculate_income_per_hour(ride); + ride->var_14D |= 2; + + if (ride->upkeep_cost != (money16)0xFFFF) + ride->profit = (money16)ride->income_per_hour - (ride->upkeep_cost * 16); + } + + // Ride specific updates + if (ride->type == RIDE_TYPE_CHAIRLIFT) + ride_chairlift_update(ride); + else if (ride->type == RIDE_TYPE_SPIRAL_SLIDE) + ride_spiral_slide_update(ride); + + ride_breakdown_update(rideIndex); + + // Various things include news messages + if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_DUE_INSPECTION)) + if (((RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) >> 1) & 255) == rideIndex) + ride_breakdown_status_update(rideIndex); + + ride_inspection_update(ride); +} + +/** + * + * rct2: 0x006AC489 + */ +static void ride_chairlift_update(rct_ride *ride) +{ + int x, y, z, ax, bx, cx; + + if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)) + return; + if (!(ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED))) + return; + if (ride->breakdown_reason_pending == 0) + return; + + ax = ride->var_0D0 * 2048; + bx = ride->var_148; + cx = bx + ax; + ride->var_148 = cx; + if (bx >> 14 == cx >> 14) + return; + + x = (ride->var_13A & 0xFF) * 32; + y = (ride->var_13A >> 8) * 32; + z = ride->var_13E * 8; + map_invalidate_tile(x, y, z, z + (4 * 8)); + + x = (ride->var_13C & 0xFF) * 32; + y = (ride->var_13C >> 8) * 32; + z = ride->var_13F * 8; + map_invalidate_tile(x, y, z, z + (4 * 8)); +} + +/** + * + * rct2: 0x006AC545 + */ +static void ride_spiral_slide_update(rct_ride *ride) +{ + int i, x, y, z; + rct_map_element *mapElement; + rct_peep *peep; + + if (RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) & 3) + return; + if (ride->var_15D == 0) + return; + + ride->var_176++; + if (ride->var_176 >= 48) { + ride->var_15D--; + + peep = &(g_sprite_list[ride->maze_tiles].peep); + peep->var_32++; + } + + // Invalidate something related to station start + for (i = 0; i < 4; i++) { + if (ride->station_starts[i] == 0xFFFF) + continue; + + x = ride->station_starts[i] & 0xFF; + y = ride->station_starts[i] >> 8; + z = ride->station_heights[i]; + + mapElement = ride_get_station_start_track_element(ride, i); + int rotation = ((mapElement->type & 3) << 2) | RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_ROTATION, uint8); + x += RCT2_GLOBAL(0x0098DDB8 + (rotation * 4), uint16); + y += RCT2_GLOBAL(0x0098DDBA + (rotation * 4), uint16); + + map_invalidate_tile(x, y, mapElement->base_height * 8, mapElement->clearance_height * 8); + } +} + +#pragma endregion + +#pragma region Breakdown and inspection functions + +static uint8 _breakdownProblemProbabilities[] = { + 25, // BREAKDOWN_SAFETY_CUT_OUT + 12, // BREAKDOWN_RESTRAINTS_STUCK_CLOSED + 10, // BREAKDOWN_RESTRAINTS_STUCK_OPEN + 13, // BREAKDOWN_DOORS_STUCK_CLOSED + 10, // BREAKDOWN_DOORS_STUCK_OPEN + 6, // BREAKDOWN_VEHICLE_MALFUNCTION + 0, // BREAKDOWN_BRAKES_FAILURE + 3 // BREAKDOWN_CONTROL_FAILURE +}; + +/** + * + * rct2: 0x006AC7C2 + */ +static void ride_inspection_update(rct_ride *ride) +{ + int i; + + if (RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) & 2047) + return; + if (RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) & SCREEN_FLAGS_TRACK_DESIGNER) + return; + + ride->last_inspection++; + if (ride->last_inspection == 0) + ride->last_inspection--; + + int inspectionIntervalMinutes = RideInspectionInterval[ride->inspection_interval]; + if (inspectionIntervalMinutes == 0) + return; + + if (RCT2_ADDRESS(0x0097C740, uint32)[ride->type] == 0) + return; + + if (inspectionIntervalMinutes > ride->last_inspection) + return; + + if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_DUE_INSPECTION | RIDE_LIFECYCLE_CRASHED)) + return; + + // Inspect the first station that has an exit + ride->lifecycle_flags |= RIDE_LIFECYCLE_DUE_INSPECTION; + ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING; + ride->inspection_station = 0; + for (i = 0; i < 4; i++) { + if (ride->exits[i] != 0xFFFF) { + ride->inspection_station = i; + break; + } + } +} + +/** + * + * rct2: 0x006AC622 + */ +static void ride_breakdown_update(int rideIndex) +{ + int agePenalty, years, ax, breakdownReason; + rct_ride *ride = GET_RIDE(rideIndex); + + if (RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) & 255) + return; + if (RCT2_GLOBAL(RCT2_ADDRESS_SCREEN_FLAGS, uint8) & SCREEN_FLAGS_TRACK_DESIGNER) + return; + + if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED)) + ride->var_19C++; + + if (!(RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) & 8191)) { + int ax = + ride->var_19C + + ride->var_19D + + ride->var_19E + + ride->var_1A0 + + ride->var_1A2 + + ride->var_1A3; + ride->var_199 = min(ax / 2, 100); + + ride->var_1A3 = ride->var_1A2; + ride->var_1A2 = ride->var_1A1; + ride->var_1A1 = ride->var_1A0; + ride->var_1A0 = ride->var_19F; + ride->var_19F = ride->var_19E; + ride->var_19E = ride->var_19D; + ride->var_19D = ride->var_19C; + ride->var_19C = 0; + ride->var_14D |= 32; + } + + if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED)) + return; + if (ride->status == RIDE_STATUS_CLOSED) + return; + + // Calculate breakdown probability? + ax = ride->var_198; + agePenalty; + years = date_get_year(RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_MONTH_YEAR, uint16) - ride->build_date); + switch (years) { + case 0: + agePenalty = 0; + break; + case 1: + agePenalty = ax >> 3; + break; + case 2: + agePenalty = ax >> 2; + break; + case 3: + agePenalty = ax >> 1; + break; + case 4: + case 5: + case 6: + agePenalty = ax >> 0; + break; + default: + agePenalty = ax << 1; + break; + } + ax += agePenalty; + ride->var_196 = max(0, ride->var_196 - ax); + ride->var_14D |= 32; + + // Random probability of a breakdown + if (ride->var_196 == 0 || (int)(scenario_rand() & 0x2FFFFF) <= 25856 - ride->var_196) { + breakdownReason = ride_get_new_breakdown_problem(ride); + if (breakdownReason != -1) + ride_prepare_breakdown(rideIndex, breakdownReason); + } +} + +/** + * + * rct2: 0x006B7294 + */ +static int ride_get_new_breakdown_problem(rct_ride *ride) +{ + int availableBreakdownProblems, monthsOld, totalProbability, randomProbability, problemBits, breakdownProblem; + rct_ride_type *entry; + + // Brake failure is more likely when its raining + _breakdownProblemProbabilities[BREAKDOWN_BRAKES_FAILURE] = + RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_RAIN_LEVEL, uint8) == 0 ? 3 : 20; + + entry = ride_get_entry(ride); + if (entry->var_008 & 0x4000) + return -1; + + availableBreakdownProblems = RideAvailableBreakdowns[ride->type]; + + // Calculate the total probability range for all possible breakdown problems + totalProbability = 0; + problemBits = availableBreakdownProblems; + while (problemBits != 0) { + breakdownProblem = bitscanforward(problemBits); + problemBits &= ~(1 << breakdownProblem); + totalProbability += _breakdownProblemProbabilities[breakdownProblem]; + } + if (totalProbability == 0) + return -1; + + // Choose a random number within this range + randomProbability = scenario_rand() % totalProbability; + + // Find which problem range the random number lies + problemBits = availableBreakdownProblems; + do { + breakdownProblem = bitscanforward(problemBits); + problemBits &= ~(1 << breakdownProblem); + randomProbability -= _breakdownProblemProbabilities[breakdownProblem]; + } while (randomProbability >= 0); + + if (breakdownProblem != BREAKDOWN_BRAKES_FAILURE) + return breakdownProblem; + + // Breaks failure can not happen if block breaks are used (so long as there is more than one vehicle) + // However if this is the case, break failure should be taken out the equation, otherwise block brake + // rides have a lower probability to break down due to a random implementation reason. + if (ride->mode == RIDE_MODE_CONTINUOUS_CIRCUIT_BLOCK_SECTIONED || ride->mode == RIDE_MODE_POWERED_LAUNCH_BLOCK_SECTIONED) + if (ride->num_vehicles != 1) + return -1; + + // Again the probability is lower, this time if young or two other unknown reasons... + monthsOld = RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_MONTH_YEAR, uint8) - ride->build_date; + if (monthsOld < 16 || ride->var_196 > 12800 || ride->lifecycle_flags & RIDE_LIFECYCLE_19) + return -1; + + return BREAKDOWN_BRAKES_FAILURE; +} + +/** + * + * rct2: 0x006B7348 + */ +static void ride_prepare_breakdown(int rideIndex, int breakdownReason) +{ + int i; + rct_ride *ride; + rct_vehicle *vehicle; + + ride = GET_RIDE(rideIndex); + if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED)) + return; + + ride->lifecycle_flags &= ~RIDE_LIFECYCLE_DUE_INSPECTION; + ride->lifecycle_flags |= RIDE_LIFECYCLE_BREAKDOWN_PENDING; + + ride->breakdown_reason_pending = breakdownReason; + ride->mechanic_status = RIDE_MECHANIC_STATUS_UNDEFINED; + ride->var_1AC = 0; + ride->var_1AD = 0; + + switch (breakdownReason) { + case BREAKDOWN_SAFETY_CUT_OUT: + case BREAKDOWN_CONTROL_FAILURE: + // Inspect first station with an exit + for (i = 0; i < 4; i++) { + if (ride->exits[i] != 0xFFFF) { + ride->inspection_station = i; + break; + } + } + break; + case BREAKDOWN_RESTRAINTS_STUCK_CLOSED: + case BREAKDOWN_RESTRAINTS_STUCK_OPEN: + case BREAKDOWN_DOORS_STUCK_CLOSED: + case BREAKDOWN_DOORS_STUCK_OPEN: + // Choose a random train and car + ride->broken_vehicle = scenario_rand() % ride->num_vehicles; + ride->broken_car = scenario_rand() % ride->num_cars_per_train; + + // Set flag on broken car + vehicle = &(g_sprite_list[ride->vehicles[ride->broken_vehicle]].vehicle); + for (i = ride->broken_car; i > 0; i--) + vehicle = &(g_sprite_list[vehicle->next_vehicle_on_train].vehicle); + vehicle->var_48 |= 0x100; + break; + case BREAKDOWN_VEHICLE_MALFUNCTION: + // Choose a random train + ride->broken_vehicle = scenario_rand() % ride->num_vehicles; + ride->broken_car = 0; + + // Set flag on broken train, first car + vehicle = &(g_sprite_list[ride->vehicles[ride->broken_vehicle]].vehicle); + vehicle->var_48 |= 0x200; + break; + case BREAKDOWN_BRAKES_FAILURE: + // Original code generates a random number but does not use it + // Unsure if this was supposed to choose a random station (or random station with an exit) + for (i = 0; i < 4; i++) { + ride->inspection_station = i; + if (ride->exits[i] != 0xFFFF) + break; + } + break; + } +} + +/** + * + * rct2: 0x006B74FA + */ +void ride_breakdown_add_news_item(int rideIndex) +{ + rct_ride *ride = GET_RIDE(rideIndex); + + RCT2_GLOBAL(0x0013CE952 + 0, uint16) = ride->name; + RCT2_GLOBAL(0x0013CE952 + 2, uint32) = ride->name_arguments; + news_item_add_to_queue(NEWS_ITEM_RIDE, 1927, rideIndex); +} + +/** + * + * rct2: 0x006B75C8 + */ +static void ride_breakdown_status_update(int rideIndex) +{ + rct_ride *ride = GET_RIDE(rideIndex); + + // Warn player if ride hasnt been fixed for ages if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) { - *formatSecondary = STR_BROKEN_DOWN; - return; - } - if (ride->status == RIDE_STATUS_CLOSED) { - *formatSecondary = STR_CLOSED; - return; - } - if (ride->status == RIDE_STATUS_TESTING) { - *formatSecondary = STR_TEST_RUN; - return; - } - rct_peep *peep = GET_PEEP(ride->race_winner); - if (ride->mode == RIDE_MODE_RACE && !(ride->lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING) && ride->race_winner != 0xFFFF && peep->sprite_identifier == SPRITE_IDENTIFIER_PEEP) { - if (peep->name_string_idx == STR_GUEST) { - *argument = peep->id; - *formatSecondary = STR_RACE_WON_BY_GUEST; - } else { - *argument = peep->name_string_idx; - *formatSecondary = STR_RACE_WON_BY; - } - } else { - if (!(RCT2_GLOBAL(RCT2_ADDRESS_RIDE_FLAGS + ride->type * 8, uint32) & 0x20000)) { - *argument = ride->num_riders; - *formatSecondary = STR_PERSON_ON_RIDE; - if(*argument != 1) - *formatSecondary = STR_PEOPLE_ON_RIDE; + ride->var_1AD++; + if (ride->var_1AD == 0) + ride->var_1AD -= 16; - } else { - *formatSecondary = STR_OPEN; + if ( + !(ride->var_1AD & 15) && + ride->mechanic_status != RIDE_MECHANIC_STATUS_FIXING && + ride->mechanic_status != RIDE_MECHANIC_STATUS_4 + ) { + RCT2_GLOBAL(0x0013CE952 + 0, uint16) = ride->name; + RCT2_GLOBAL(0x0013CE952 + 2, uint32) = ride->name_arguments; + news_item_add_to_queue(NEWS_ITEM_RIDE, 1929, rideIndex); } } + + ride_mechanic_status_update(rideIndex, ride->mechanic_status); +} + +/** + * + * rct2: 0x006B762F + */ +static void ride_mechanic_status_update(int rideIndex, int mechanicStatus) +{ + int breakdownReason; + rct_ride *ride; + rct_peep *mechanic; + + ride = GET_RIDE(rideIndex); + switch (mechanicStatus) { + case RIDE_MECHANIC_STATUS_UNDEFINED: + breakdownReason = ride->breakdown_reason_pending; + if ( + breakdownReason == BREAKDOWN_SAFETY_CUT_OUT || + breakdownReason == BREAKDOWN_BRAKES_FAILURE || + breakdownReason == BREAKDOWN_CONTROL_FAILURE + ) { + ride->lifecycle_flags |= RIDE_LIFECYCLE_BROKEN_DOWN; + ride->var_14D |= 0x2C; + ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING; + ride->breakdown_reason = breakdownReason; + ride_breakdown_add_news_item(rideIndex); + } + break; + case RIDE_MECHANIC_STATUS_CALLING: + if (RideAvailableBreakdowns[ride->type] == 0) { + ride->lifecycle_flags &= ~(RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_DUE_INSPECTION); + break; + } + + ride_call_mechanic(rideIndex); + break; + case RIDE_MECHANIC_STATUS_HEADING: + mechanic = &(g_sprite_list[ride->mechanic].peep); + if ( + !peep_is_mechanic(mechanic) || + (mechanic->state != PEEP_STATE_HEADING_TO_INSPECTION && mechanic->state != PEEP_STATE_ANSWERING) || + mechanic->current_ride != rideIndex + ) { + ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING; + ride->var_14D |= 0x20; + ride_mechanic_status_update(rideIndex, RIDE_MECHANIC_STATUS_CALLING); + } + break; + case RIDE_MECHANIC_STATUS_FIXING: + mechanic = &(g_sprite_list[ride->mechanic].peep); + if ( + !peep_is_mechanic(mechanic) || + ( + mechanic->state != PEEP_STATE_HEADING_TO_INSPECTION && + mechanic->state != PEEP_STATE_FIXING && + mechanic->state != PEEP_STATE_INSPECTING && + mechanic->state != PEEP_STATE_ANSWERING + ) + ) { + ride->mechanic_status = RIDE_MECHANIC_STATUS_CALLING; + ride->var_14D |= 0x20; + ride_mechanic_status_update(rideIndex, RIDE_MECHANIC_STATUS_CALLING); + } + break; + } +} + +/** + * + * rct2: 0x006B76AB + */ +static void ride_call_mechanic(int rideIndex) +{ + int x, y, z, stationIndex, direction, inspecting; + uint16 xy; + rct_ride *ride; + rct_map_element *mapElement; + rct_peep *mechanic; + + ride = GET_RIDE(rideIndex); + + inspecting = (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN)) == 0; + + // Get either exit position or entrance position if there is no exit + stationIndex = ride->inspection_station; + xy = ride->exits[stationIndex]; + if (xy == 0xFFFF) { + xy = ride->entrances[stationIndex]; + if (xy == 0xFFFF) + return; + } + + // Get station start track element and position + x = (xy & 0xFF) * 32; + y = (xy >> 8) * 32; + z = ride->station_heights[stationIndex] * 8; + mapElement = ride_get_station_start_track_element(ride, stationIndex); + if (mapElement == NULL) + return; + + direction = mapElement->type & 3; + x -= RCT2_ADDRESS(0x00993CCC, sint16)[direction * 2]; + y -= RCT2_ADDRESS(0x00993CCE, sint16)[direction * 2]; + x += 16; + y += 16; + + // Find closest mechanic + mechanic = find_closest_mechanic(x, y, inspecting); + if (mechanic == NULL) + return; + + RCT2_CALLPROC_X(0x0069A409, 0, 0, 0, 0, (int)mechanic, 0, 0); + mechanic->state = inspecting ? PEEP_STATE_HEADING_TO_INSPECTION : PEEP_STATE_ANSWERING; + RCT2_CALLPROC_X(0x0069A42F, 0, 0, 0, 0, (int)mechanic, 0, 0); + mechanic->var_2C = 0; + ride->mechanic_status = RIDE_MECHANIC_STATUS_HEADING; + ride->var_14D |= 0x20; + ride->mechanic = mechanic->sprite_index; + mechanic->current_ride = rideIndex; + mechanic->current_ride_station = ride->inspection_station; +} + +/** + * + * rct2: 0x006B774B (forInspection = 0) + * rct2: 0x006B78C3 (forInspection = 1) + */ +rct_peep *find_closest_mechanic(int x, int y, int forInspection) +{ + unsigned int closestDistance, distance; + uint16 spriteIndex; + rct_peep *peep, *closestMechanic; + + closestDistance = -1; + FOR_ALL_STAFF(spriteIndex, peep) { + if (forInspection) { + if ((peep->state != PEEP_STATE_HEADING_TO_INSPECTION || peep->var_2C >= 4) && peep->state != PEEP_STATE_PATROLLING) + continue; + + if (!(peep->staff_orders & 2)) + continue; + } else { + if (peep->state != PEEP_STATE_PATROLLING && !(peep->staff_orders & 1)) + continue; + } + + if (map_is_location_in_park(x, y)) + if (!mechanic_is_location_in_patrol(peep, x & 0xFFE0, y & 0xFFE0)) + continue; + + if (peep->x == (sint16)0x8000) + continue; + + // Should probably be euclidean or manhattan distance, this seems a bit naive + distance = max(abs(peep->x - x), abs(peep->y - y)); + if (distance < closestDistance) { + closestDistance = distance; + closestMechanic = peep; + } + } + + return closestDistance == -1 ? NULL : closestMechanic; } rct_peep *ride_get_assigned_mechanic(rct_ride *ride) @@ -547,118 +1151,88 @@ rct_peep *ride_get_assigned_mechanic(rct_ride *ride) return NULL; } -int ride_get_total_length(rct_ride *ride) -{ - int i, totalLength = 0; - for (i = 0; i < ride->num_stations; i++) - totalLength += ride->length[i]; - return totalLength; -} +#pragma endregion -int ride_can_have_multiple_circuits(rct_ride *ride) -{ - if (!(RCT2_GLOBAL(0x0097D4F2 + (ride->type * 8), uint16) & 0x200)) - return 0; - - // Only allow circuit or launch modes - if ( - ride->mode != RIDE_MODE_CONTINUOUS_CIRCUIT && - ride->mode != RIDE_MODE_REVERSE_INCLINE_LAUNCHED_SHUTTLE && - ride->mode != RIDE_MODE_POWERED_LAUNCH - ) { - return 0; - } - - // Must have no more than one vehicle and one station - if (ride->num_vehicles > 1 || ride->num_stations > 1) - return 0; - - return 1; -} - -track_colour ride_get_track_colour(rct_ride *ride, int colourScheme) -{ - track_colour result; - result.main = ride->track_colour_main[colourScheme]; - result.additional = ride->track_colour_additional[colourScheme]; - result.supports = ride->track_colour_supports[colourScheme]; - return result; -} - -vehicle_colour ride_get_vehicle_colour(rct_ride *ride, int vehicleIndex) -{ - vehicle_colour result; - result.main = ride->vehicle_colours[vehicleIndex] & 0xFF; - result.additional_1 = ride->vehicle_colours[vehicleIndex] >> 8; - result.additional_2 = ride->vehicle_colours_extended[vehicleIndex]; - return result; -} +#pragma region Music functions /** * - * rct2: 0x006AC988 - * set the speed of the go kart type vehicle at the start to a random value or alter if peep name is an easter egg - * @param ride (esi) + * rct2: 0x006ABE85 */ -void ride_init_vehicle_speed(rct_ride *ride) +static void ride_music_update(int rideIndex) { - rct_ride_type *rideEntry; + int x, y, z; rct_vehicle *vehicle; - uint8 *unk; - int i; + rct_ride *ride = GET_RIDE(rideIndex); - for (i = 0; i < ride->num_vehicles; i++) { - vehicle = &g_sprite_list[ride->vehicles[i]].vehicle; - vehicle->var_48 &= ~(1 << 6); + if (!(RCT2_GLOBAL(0x0097D4F2 + (ride->type * 8), uint16) & 6)) + return; - rideEntry = GET_RIDE_ENTRY(vehicle->var_D6); - unk = (uint8*)((int)rideEntry + (vehicle->var_31 * 0x65)); + if (ride->status != RIDE_STATUS_OPEN || !(ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC)) { + ride->music_tune_id = 255; + return; + } - vehicle->speed = (scenario_rand() & 16) - 8 + RCT2_GLOBAL(unk + 0x76, uint8); + if (ride->type == RIDE_TYPE_CIRCUS_SHOW) { + vehicle = &(g_sprite_list[ride->vehicles[0]].vehicle); + if (vehicle->status != VEHICLE_STATUS_DOING_CIRCUS_SHOW) { + ride->music_tune_id = 255; + return; + } + } - if (vehicle->var_B3) { - rct_peep *peep = &g_sprite_list[vehicle->peep].peep; + // Oscillate parameters for a power cut effect when breaking down + if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN)) { + if (ride->breakdown_reason_pending == BREAKDOWN_CONTROL_FAILURE) { + if (!(RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) & 7)) + if (ride->var_1AC != 255) + ride->var_1AC++; + } else { + if ( + (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) || + ride->breakdown_reason_pending == BREAKDOWN_BRAKES_FAILURE || + ride->breakdown_reason_pending == BREAKDOWN_CONTROL_FAILURE + ) { + if (ride->var_1AC != 255) + ride->var_1AC++; + } - switch (peep_get_easteregg_name_id(peep)) { - case EASTEREGG_PEEP_NAME_MICHAEL_SCHUMACHER: - vehicle->speed += 35; - break; - case EASTEREGG_PEEP_NAME_JACQUES_VILLENEUVE: - vehicle->speed += 25; - break; - case EASTEREGG_PEEP_NAME_DAMON_HILL: - vehicle->speed += 55; - break; - case EASTEREGG_PEEP_NAME_CHRIS_SAWYER: - vehicle->speed += 14; - break; - case EASTEREGG_PEEP_NAME_MR_BEAN: - vehicle->speed = 9; - break; + if (ride->var_1AC == 255) { + ride->music_tune_id = 255; + return; } } } -} -rct_ride_type *ride_get_entry(rct_ride *ride) -{ - return GET_RIDE_ENTRY(ride->subtype); -} - -uint8 *get_ride_entry_indices_for_ride_type(uint8 rideType) -{ - uint8 *typeToRideEntryIndexMap = (uint8*)0x009E32F8; - uint8 *entryIndexList = typeToRideEntryIndexMap; - while (rideType > 0) { - do { - entryIndexList++; - } while (*(entryIndexList - 1) != 255); - rideType--; + // Select random tune from available tunes for a music style (of course only merry-go-rounds have more than one tune) + if (ride->music_tune_id == 255) { + uint8 *musicStyleTunes = RCT2_ADDRESS(0x009AEF28, uint8*)[ride->music]; + uint8 numTunes = *musicStyleTunes++; + ride->music_tune_id = musicStyleTunes[scenario_rand() % numTunes]; + ride->music_position = 0; + return; } - return entryIndexList; + + x = (ride->station_starts[0] & 0xFF) * 32 + 16; + y = (ride->station_starts[0] >> 8) * 32 + 16; + z = ride->station_heights[0] * 8; + + int sampleRate = 22050; + + // Alter sample rate for a power cut effect + if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN)) { + sampleRate = ride->var_1AC * 70; + if (ride->breakdown_reason_pending != BREAKDOWN_CONTROL_FAILURE) + sampleRate *= -1; + sampleRate += 22050; + } + + ride->music_position = sub_6BC3AC(x, y, z, rideIndex, sampleRate, ride->music_position, &ride->music_tune_id); } +#pragma endregion +#pragma region Measurement functions /** * rct2: 0x006B64F2 @@ -778,6 +1352,34 @@ void ride_measurements_update() } } +rct_ride_measurement *ride_get_existing_measurement(int rideIndex) +{ + int i; + rct_ride_measurement *measurement; + + for (i = 0; i < MAX_RIDE_MEASUREMENTS; i++) { + measurement = GET_RIDE_MEASUREMENT(i); + if (measurement->ride_index == rideIndex) + return measurement; + } + + return NULL; +} + +rct_ride_measurement *ride_get_free_measurement() +{ + int i; + rct_ride_measurement *measurement; + + for (i = 0; i < MAX_RIDE_MEASUREMENTS; i++) { + measurement = GET_RIDE_MEASUREMENT(i); + if (measurement->ride_index == 255) + return measurement; + } + + return NULL; +} + /** * * rct2: 0x006B66D9 @@ -798,45 +1400,37 @@ rct_ride_measurement *ride_get_measurement(int rideIndex, rct_string_id *message } // Check if a measurement already exists for this ride - for (i = 0; i < MAX_RIDE_MEASUREMENTS; i++) { - measurement = GET_RIDE_MEASUREMENT(i); - if (measurement->ride_index == i) - goto use_measurement; - } + measurement = ride_get_existing_measurement(rideIndex); + if (measurement == NULL) { + // Find a free measurement + measurement = ride_get_free_measurement(); + if (measurement == NULL) { + // Use last recently used measurement for some other ride + lruIndex = 0; + lruTicks = 0xFFFFFFFF; + for (i = 0; i < MAX_RIDE_MEASUREMENTS; i++) { + measurement = GET_RIDE_MEASUREMENT(i); - // Find a free measurement - for (i = 0; i < MAX_RIDE_MEASUREMENTS; i++) { - measurement = GET_RIDE_MEASUREMENT(i); - if (measurement->ride_index == 255) - goto new_measurement; - } + if (measurement->last_use_tick <= lruTicks) { + lruTicks = measurement->last_use_tick; + lruIndex = i; + } + } - // Use last recently used measurement for some other ride - lruIndex = 0; - lruTicks = 0xFFFFFFFF; - for (i = 0; i < MAX_RIDE_MEASUREMENTS; i++) { - measurement = GET_RIDE_MEASUREMENT(i); - - if (measurement->last_use_tick <= lruTicks) { - lruTicks = measurement->last_use_tick; - lruIndex = i; + i = lruIndex; + measurement = GET_RIDE_MEASUREMENT(i); + ride->measurement_index = 255; } + + measurement->ride_index = rideIndex; + ride->measurement_index = i; + measurement->flags = 0; + if (RCT2_GLOBAL(RCT2_ADDRESS_RIDE_FLAGS + (ride->type * 8), uint32) & 0x80) + measurement->flags |= RIDE_MEASUREMENT_FLAG_G_FORCES; + measurement->num_items = 0; + measurement->current_item = 0; } - i = lruIndex; - measurement = GET_RIDE_MEASUREMENT(i); - ride->measurement_index = 255; - -new_measurement: - measurement->ride_index = rideIndex; - ride->measurement_index = i; - measurement->flags = 0; - if (RCT2_GLOBAL(RCT2_ADDRESS_RIDE_FLAGS + (ride->type * 8), uint32) & 0x80) - measurement->flags |= RIDE_MEASUREMENT_FLAG_G_FORCES; - measurement->num_items = 0; - measurement->current_item = 0; - -use_measurement: measurement->last_use_tick = RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TICKS, uint32); if (measurement->flags & 1) { if (message != NULL) *message = 0; @@ -847,4 +1441,172 @@ use_measurement: if (message != NULL) *message = STR_DATA_LOGGING_WILL_START_WHEN_NEXT_LEAVES; return NULL; } -} \ No newline at end of file +} + +#pragma endregion + +#pragma region Colour functions + +track_colour ride_get_track_colour(rct_ride *ride, int colourScheme) +{ + track_colour result; + result.main = ride->track_colour_main[colourScheme]; + result.additional = ride->track_colour_additional[colourScheme]; + result.supports = ride->track_colour_supports[colourScheme]; + return result; +} + +vehicle_colour ride_get_vehicle_colour(rct_ride *ride, int vehicleIndex) +{ + vehicle_colour result; + result.main = ride->vehicle_colours[vehicleIndex] & 0xFF; + result.additional_1 = ride->vehicle_colours[vehicleIndex] >> 8; + result.additional_2 = ride->vehicle_colours_extended[vehicleIndex]; + return result; +} + +#pragma endregion + +#pragma region Reachability + +/** + * rct2: 0x006B7A5E + **/ +void ride_check_all_reachable() +{ + rct_ride *ride; + int i; + + FOR_ALL_RIDES(i, ride) { + if (ride->connected_message_throttle != 0) + ride->connected_message_throttle--; + if (ride->status != RIDE_STATUS_OPEN || ride->connected_message_throttle != 0) + continue; + + if (RCT2_GLOBAL(RCT2_ADDRESS_RIDE_FLAGS + ride->type * 8, uint32) & 0x20000) + ride_shop_connected(ride, i); + else + ride_entrance_exit_connected(ride, i); + } +} + +/** + * rct2: 0x006B7C59 + * @return 1 if the coordinate is reachable or has no entrance, 0 otherwise + */ +static int ride_entrance_exit_is_reachable(uint16 coordinate, rct_ride* ride, int index) { + int x = ((coordinate >> 8) & 0xFF) << 5, // cx + y = (coordinate & 0xFF) << 5; // ax + uint8 station_height = ride->station_heights[index]; + int tile_idx = ((x << 8) | y) >> 5; + rct_map_element* tile = RCT2_ADDRESS(RCT2_ADDRESS_TILE_MAP_ELEMENT_POINTERS, rct_map_element*)[tile_idx]; + + while(1) { + uint8 element_type = tile->type & MAP_ELEMENT_TYPE_MASK; + if (element_type == MAP_ELEMENT_TYPE_ENTRANCE && station_height == tile->base_height) { + break; + } else if (tile->flags & MAP_ELEMENT_FLAG_LAST_TILE) { + return 1; + } + tile++; + } + + uint8 face_direction = tile->type & 3; + y -= RCT2_ADDRESS(0x00993CCC, sint16)[face_direction * 2]; + x -= RCT2_ADDRESS(0x00993CCE, sint16)[face_direction * 2]; + tile_idx = ((x << 8) | y) >> 5; + + return map_coord_is_connected(tile_idx, station_height, face_direction); +} + +static void ride_entrance_exit_connected(rct_ride* ride, int ride_idx) +{ + for (int i = 0; i < 4; ++i) { + uint16 station_start = ride->station_starts[i], + entrance = ride->entrances[i], + exit = ride->exits[i]; + + if (station_start == -1 ) + continue; + if (entrance != -1 && !ride_entrance_exit_is_reachable(entrance, ride, i)) { + // name of ride is parameter of the format string + RCT2_GLOBAL(0x013CE952, uint16) = ride->name; + RCT2_GLOBAL(0x013CE954, uint32) = ride->name_arguments; + news_item_add_to_queue(1, STR_ENTRANCE_NOT_CONNECTED, ride_idx); + ride->connected_message_throttle = 3; + } + + if (exit != -1 && !ride_entrance_exit_is_reachable(exit, ride, i)) { + // name of ride is parameter of the format string + RCT2_GLOBAL(0x013CE952, uint16) = ride->name; + RCT2_GLOBAL(0x013CE954, uint32) = ride->name_arguments; + news_item_add_to_queue(1, STR_EXIT_NOT_CONNECTED, ride_idx); + ride->connected_message_throttle = 3; + } + + } +} + +static void ride_shop_connected(rct_ride* ride, int ride_idx) +{ + rct_ride* ride_back = ride; + uint16 coordinate = ride->station_starts[0]; + if (coordinate == 0xFFFF) + return; + + int x = ((coordinate >> 8) & 0xFF) << 5, // cx + y = (coordinate & 0xFF) << 5; // ax + + rct_map_element* tile = RCT2_ADDRESS(RCT2_ADDRESS_TILE_MAP_ELEMENT_POINTERS, rct_map_element*)[coordinate]; + + for (; ; tile++){ + uint8 element_type = tile->type & MAP_ELEMENT_TYPE_MASK; + if(element_type == MAP_ELEMENT_TYPE_TRACK && tile->properties.track.ride_index == ride_idx) + break; + if(tile->flags & MAP_ELEMENT_FLAG_LAST_TILE) + return; + } + + uint16 entrance_directions = 0; + uint8 track_type = tile->properties.track.type; + ride = &g_ride_list[tile->properties.track.ride_index]; + if (RCT2_GLOBAL(RCT2_ADDRESS_RIDE_FLAGS + ride->type * 8, uint32) & 0x80000) { + entrance_directions = RCT2_ADDRESS(0x0099CA64, uint8)[track_type * 16]; + } else { + entrance_directions = RCT2_ADDRESS(0x0099BA64, uint8)[track_type * 16]; + } + + + uint8 tile_direction = tile->type & MAP_ELEMENT_DIRECTION_MASK; + entrance_directions <<= tile_direction; + entrance_directions = ((entrance_directions >> 12) | entrance_directions) & 0xF; + + // now each bit in entrance_directions stands for an entrance direction to check + if (entrance_directions == 0) + return; + + for (int count = 0; entrance_directions != 0; ++count) { + if (!(entrance_directions & 1)) { + entrance_directions >>= 1; + continue; + } + entrance_directions >>= 1; + + uint8 face_direction = count ^ 2; // flip direction north<->south, east<->west + int y2 = y - RCT2_ADDRESS(0x00993CCC, sint16)[face_direction * 2]; + int x2 = x - RCT2_ADDRESS(0x00993CCE, sint16)[face_direction * 2]; + int tile_idx = ((x2 << 8) | y2) >> 5; + + if (map_coord_is_connected(tile_idx, tile->base_height, face_direction)) + return; + } + + // name of ride is parameter of the format string + RCT2_GLOBAL(0x013CE952, uint16) = ride->name; + RCT2_GLOBAL(0x013CE954, uint32) = ride->name_arguments; + news_item_add_to_queue(1, STR_ENTRANCE_NOT_CONNECTED, ride_idx); + + ride->connected_message_throttle = 3; +} + +#pragma endregion \ No newline at end of file diff --git a/src/ride/ride.h b/src/ride/ride.h index fe255e6920..f87a90d903 100644 --- a/src/ride/ride.h +++ b/src/ride/ride.h @@ -97,7 +97,7 @@ typedef struct { uint16 station_starts[4]; // 0x052 uint8 station_heights[4]; // 0x05A uint8 pad_05E[0x4]; - uint8 var_062[4]; + uint8 station_depart[4]; // 0x062 uint8 pad_066[0x4]; uint16 entrances[4]; // 0x06A uint16 exits[4]; // 0x072 @@ -114,7 +114,11 @@ typedef struct { uint8 var_0CD; uint8 min_waiting_time; // 0x0CE uint8 max_waiting_time; // 0x0CF - uint8 var_0D0; + union { + uint8 var_0D0; + uint8 time_limit; // 0x0D0 + uint8 num_laps; // 0x0D0 + }; uint8 pad_0D1[0x3]; uint8 measurement_index; // 0x0D4 uint8 var_0D5; @@ -178,20 +182,26 @@ typedef struct { uint16 var_158; uint8 pad_15A; uint8 num_riders; // 0x15B - uint8 var_15C; + uint8 music_tune_id; // 0x15C uint8 var_15D; - uint16 maze_tiles; // 0x15E + union { + uint16 slide_peep; // 0x15E + uint16 maze_tiles; // 0x15E + }; uint8 pad_160[0x16]; uint8 var_176; uint8 pad_177[0x9]; sint16 build_date; // 0x180 money16 upkeep_cost; // 0x182 uint16 race_winner; // 0x184 - uint8 pad_186[0x06]; - uint8 var_18C; + uint8 pad_186[0x02]; + uint32 music_position; // 0x188 + uint8 breakdown_reason_pending; // 0x18C uint8 mechanic_status; // 0x18D uint16 mechanic; // 0x18E - uint8 pad_190[0x03]; + uint8 inspection_station; // 0x190 + uint8 broken_vehicle; // 0x191 + uint8 broken_car; // 0x192 uint8 breakdown_reason; // 0x193 money16 price_secondary; // 0x194 uint16 var_196; @@ -200,9 +210,18 @@ typedef struct { uint8 var_199; uint8 inspection_interval; // 0x19A uint8 last_inspection; // 0x19B - uint8 pad_19C[0x8]; + uint8 var_19C; + uint8 var_19D; + uint8 var_19E; + uint8 var_19F; + uint8 var_1A0; + uint8 var_1A1; + uint8 var_1A2; + uint8 var_1A3; uint32 var_1A4; - uint8 pad_1A8[6]; + uint8 pad_1A8[4]; + uint8 var_1AC; + uint8 var_1AD; uint8 var_1AE; uint8 connected_message_throttle; // 0x1AF money32 income_per_hour; // 0x1B0 @@ -261,8 +280,9 @@ enum { RIDE_LIFECYCLE_NO_RAW_STATS = 1 << 3, RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING = 1 << 4, RIDE_LIFECYCLE_ON_RIDE_PHOTO = 1 << 5, - + RIDE_LIFECYCLE_BREAKDOWN_PENDING = 1 << 6, RIDE_LIFECYCLE_BROKEN_DOWN = 1 << 7, + RIDE_LIFECYCLE_DUE_INSPECTION = 1 << 8, RIDE_LIFECYCLE_CRASHED = 1 << 10, RIDE_LIFECYCLE_11 = 1 << 11, @@ -480,9 +500,11 @@ enum { }; enum { - RIDE_MECHANIC_STATUS_CALLING = 1, - RIDE_MECHANIC_STATUS_HEADING = 2, - RIDE_MECHANIC_STATUS_FIXING = 3, + RIDE_MECHANIC_STATUS_UNDEFINED, + RIDE_MECHANIC_STATUS_CALLING, + RIDE_MECHANIC_STATUS_HEADING, + RIDE_MECHANIC_STATUS_FIXING, + RIDE_MECHANIC_STATUS_4 }; enum { @@ -522,6 +544,16 @@ enum { RIDE_ENTRANCE_STYLE_SPACE }; +enum { + RIDE_INSPECTION_EVERY_10_MINUTES, + RIDE_INSPECTION_EVERY_20_MINUTES, + RIDE_INSPECTION_EVERY_30_MINUTES, + RIDE_INSPECTION_EVERY_45_MINUTES, + RIDE_INSPECTION_EVERY_HOUR, + RIDE_INSPECTION_EVERY_2_HOURS, + RIDE_INSPECTION_NEVER +}; + typedef struct { uint8 main; uint8 additional; @@ -545,6 +577,9 @@ enum { #define MAX_RIDE_MEASUREMENTS 8 #define RIDE_RELIABILITY_UNDEFINED 0xFFFF +#define STATION_DEPART_FLAG (1 << 7) +#define STATION_DEPART_MASK (~STATION_DEPART_FLAG) + // rct2: 0x009ACFA4 rct_ride_type **gRideTypeList; @@ -587,5 +622,6 @@ rct_ride_type *ride_get_entry(rct_ride *ride); uint8 *get_ride_entry_indices_for_ride_type(uint8 rideType); void ride_measurements_update(); rct_ride_measurement *ride_get_measurement(int rideIndex, rct_string_id *message); +void ride_breakdown_add_news_item(int rideIndex); #endif diff --git a/src/ride/ride_data.c b/src/ride/ride_data.c index b1ae1d8b85..d6645eef1a 100644 --- a/src/ride/ride_data.c +++ b/src/ride/ride_data.c @@ -771,4 +771,98 @@ const uint8 RideAvailableModes[] = { RIDE_MODE_CONTINUOUS_CIRCUIT, 0xFF, // 58 Mine Ride RIDE_MODE_CONTINUOUS_CIRCUIT, RIDE_MODE_CONTINUOUS_CIRCUIT_BLOCK_SECTIONED, 0xFF, // 59 LIM Launched Roller Coaster RIDE_MODE_POWERED_LAUNCH_35, RIDE_MODE_POWERED_LAUNCH_BLOCK_SECTIONED, 0xFF // 60 (none) +}; + +const uint8 RideAvailableBreakdowns[] = { + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 00 Spiral Roller coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 01 Stand Up Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 02 Suspended Swinging + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 03 Inverted + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 04 Steel Mini Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 05 Mini Railroad + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_DOORS_STUCK_CLOSED) | (1 << BREAKDOWN_DOORS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 06 Monorail + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 07 Mini Suspended Coaster + (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 08 Bumper Boats + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 09 Wooden Wild Mine/Mouse + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 0A Steeplechase/Motorbike/Soap Box Derby + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 0B Car Ride + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 0C Launched Freefall + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 0D Bobsleigh Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 0E Observation Tower + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 0F Looping Roller Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 10 Dinghy Slide + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 11 Mine Train Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 12 Chairlift + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 13 Corkscrew Roller Coaster + 0, // 14 Maze + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 15 Spiral Slide + (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 16 Go Karts + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_CONTROL_FAILURE), // 17 Log Flume + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_CONTROL_FAILURE), // 18 River Rapids + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 19 Bumper Cars + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 1A Pirate Ship + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 1B Swinging Inverter Ship + 0, // 1C Food Stall + 0, // 1D (none) + 0, // 1E Drink Stall + 0, // 1F (none) + 0, // 20 Shop (all types) + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_CONTROL_FAILURE), // 21 Merry Go Round + 0, // 22 Balloon Stall (maybe) + 0, // 23 Information Kiosk + 0, // 24 Bathroom + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 25 Ferris Wheel + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 26 Motion Simulator + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 27 3D Cinema + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 28 Gravitron + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 29 Space Rings + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 2A Reverse Freefall Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_DOORS_STUCK_CLOSED) | (1 << BREAKDOWN_DOORS_STUCK_OPEN), // 2B Elevator + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 2C Vertical Drop Roller Coaster + 0, // 2D ATM + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 2E Twist + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 2F Haunted House + 0, // 30 First Aid + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 31 Circus Show + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 32 Ghost Train + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 33 Twister Roller Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 34 Wooden Roller Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 35 Side-Friction Roller Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 36 Wild Mouse + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 37 Multi Dimension Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 38 (none) + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 39 Flying Roller Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 3A (none) + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 3B Virginia Reel + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_CONTROL_FAILURE), // 3C Splash Boats + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 3D Mini Helicopters + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 3E Lay-down Roller Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_DOORS_STUCK_CLOSED) | (1 << BREAKDOWN_DOORS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 3F Suspended Monorail + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 40 (none) + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 41 Reverser Roller Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 42 Heartline Twister Roller Coaster + 0, // 43 Mini Golf + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 44 Giga Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 45 Roto-Drop + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 46 Flying Saucers + 0, // 47 Crooked House + (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 48 Monorail Cycles + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 49 Compact Inverted Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 4A Water Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 4B Air Powered Vertical Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 4C Inverted Hairpin Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 4D Magic Carpet + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 4E Submarine Ride + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 4F River Rafts + 0, // 50 (none) + (1 << BREAKDOWN_SAFETY_CUT_OUT), // 51 Enterprise + 0, // 52 (none) + 0, // 53 (none) + 0, // 54 (none) + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 55 (none) + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 56 Inverted Impulse Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 57 Mini Roller Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION), // 58 Mine Ride + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE), // 59 LIM Launched Roller Coaster + (1 << BREAKDOWN_SAFETY_CUT_OUT) | (1 << BREAKDOWN_RESTRAINTS_STUCK_CLOSED) | (1 << BREAKDOWN_RESTRAINTS_STUCK_OPEN) | (1 << BREAKDOWN_VEHICLE_MALFUNCTION) | (1 << BREAKDOWN_BRAKES_FAILURE) // 60 (none) }; \ No newline at end of file diff --git a/src/ride/ride_data.h b/src/ride/ride_data.h index d6b1617b04..d45438480d 100644 --- a/src/ride/ride_data.h +++ b/src/ride/ride_data.h @@ -41,5 +41,6 @@ extern const uint8 rideUnknownData3[0x60]; extern const rct_ride_name_convention RideNameConvention[96]; extern const uint8 RideAvailableModes[]; +extern const uint8 RideAvailableBreakdowns[]; #endif \ No newline at end of file diff --git a/src/ride/station.c b/src/ride/station.c new file mode 100644 index 0000000000..38b7b5f792 --- /dev/null +++ b/src/ride/station.c @@ -0,0 +1,309 @@ +/***************************************************************************** + * Copyright (c) 2014 Ted John + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * This file is part of OpenRCT2. + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *****************************************************************************/ + +#include "../addresses.h" +#include "../scenario.h" +#include "../world/sprite.h" +#include "station.h" + +static void ride_update_station_blocksection(rct_ride *ride, int stationIndex); +static void ride_update_station_bumpercar(rct_ride *ride, int stationIndex); +static void ride_update_station_normal(rct_ride *ride, int stationIndex); +static void ride_update_station_race(rct_ride *ride, int stationIndex); +static void ride_race_init_vehicle_speeds(rct_ride *ride); +static void ride_invalidate_station_start(rct_ride *ride, int stationIndex, int dl); + +/** + * + * rct2: 0x006ABFFB + */ +void ride_update_station(rct_ride *ride, int stationIndex) +{ + if (ride->station_starts[stationIndex] == 0xFFFF) + return; + + switch (ride->mode) { + case RIDE_MODE_RACE: + ride_update_station_race(ride, stationIndex); + break; + case RIDE_MODE_BUMPERCAR: + ride_update_station_bumpercar(ride, stationIndex); + break; + case RIDE_MODE_CONTINUOUS_CIRCUIT_BLOCK_SECTIONED: + case RIDE_MODE_POWERED_LAUNCH_BLOCK_SECTIONED: + ride_update_station_blocksection(ride, stationIndex); + break; + default: + ride_update_station_normal(ride, stationIndex); + break; + } +} + +/** + * + * rct2: 0x006AC0A1 + */ +static void ride_update_station_blocksection(rct_ride *ride, int stationIndex) +{ + rct_map_element *mapElement; + + mapElement = ride_get_station_start_track_element(ride, stationIndex); + + if ((ride->status == RIDE_STATUS_CLOSED && ride->num_riders == 0) || mapElement->flags & 0x20) { + ride->station_depart[stationIndex] &= ~STATION_DEPART_FLAG; + + if ((ride->station_depart[stationIndex] & STATION_DEPART_FLAG) || (mapElement->properties.track.sequence & 0x80)) + ride_invalidate_station_start(ride, stationIndex, 0); + } else { + if (!(ride->station_depart[stationIndex] & STATION_DEPART_FLAG)) { + ride->station_depart[stationIndex] |= STATION_DEPART_FLAG; + ride_invalidate_station_start(ride, stationIndex, 1); + } else if (mapElement->properties.track.sequence & 0x80) { + ride_invalidate_station_start(ride, stationIndex, 1); + } + } +} + +/** + * + * rct2: 0x006AC12B + */ +static void ride_update_station_bumpercar(rct_ride *ride, int stationIndex) +{ + int i, dx, dl, dh; + rct_vehicle *vehicle; + + if ( + ride->status == RIDE_STATUS_CLOSED || + (ride->lifecycle_flags & (RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED)) + ) { + ride->station_depart[stationIndex] &= ~STATION_DEPART_FLAG; + return; + } + + if (ride->lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING) { + dx = ride->time_limit * 32; + dl = dx & 0xFF; + dh = (dx >> 8) & 0xFF; + for (i = 0; i < ride->num_vehicles; i++) { + vehicle = &(g_sprite_list[ride->vehicles[i]].vehicle); + if (vehicle->var_CE < dh || (vehicle->var_CE < dh && vehicle->var_51 > dl)) + continue; + + // End match + ride->lifecycle_flags &= ~RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING; + ride->station_depart[stationIndex] &= ~STATION_DEPART_FLAG; + return; + } + + // Continue match + ride->station_depart[stationIndex] |= STATION_DEPART_FLAG; + } else { + // Check if all vehicles are ready to go + for (i = 0; i < ride->num_vehicles; i++) { + vehicle = &(g_sprite_list[ride->vehicles[i]].vehicle); + if (vehicle->status != VEHICLE_STATUS_WAITING_TO_DEPART) { + ride->station_depart[stationIndex] &= ~STATION_DEPART_FLAG; + return; + } + } + + // Begin the match + ride->lifecycle_flags |= RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING; + ride->station_depart[stationIndex] |= STATION_DEPART_FLAG; + ride->var_14D |= 12; + } +} + +/** + * + * rct2: 0x006AC02C + */ +static void ride_update_station_normal(rct_ride *ride, int stationIndex) +{ + int time; + + time = ride->station_depart[stationIndex] & STATION_DEPART_MASK; + if ( + (ride->lifecycle_flags & (RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED)) && + (ride->status == RIDE_STATUS_CLOSED && ride->num_riders == 0) + ) { + if (time != 0 && time != 127 && !(RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) & 7)) + time--; + + ride->station_depart[stationIndex] = time; + } else { + if (time == 0) { + ride->station_depart[stationIndex] |= STATION_DEPART_FLAG; + } else { + if (time != 127 && !(RCT2_GLOBAL(RCT2_ADDRESS_CURRENT_TICKS, uint32) & 31)) + time--; + + ride->station_depart[stationIndex] = time; + } + } +} + +/** + * + * rct2: 0x006AC1DF + */ +static void ride_update_station_race(rct_ride *ride, int stationIndex) +{ + int i, numLaps; + rct_vehicle *vehicle; + rct_peep *peep; + + if ( + ride->status == RIDE_STATUS_CLOSED || + (ride->lifecycle_flags & (RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED)) + ) { + ride->station_depart[stationIndex] &= ~STATION_DEPART_FLAG; + return; + } + + if (ride->lifecycle_flags & RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING) { + numLaps = ride->num_laps; + for (i = 0; i < ride->num_vehicles; i++) { + vehicle = &(g_sprite_list[ride->vehicles[i]].vehicle); + if (vehicle->status != VEHICLE_STATUS_WAITING_TO_DEPART && vehicle->num_laps >= numLaps) { + // Found a winner + if (vehicle->var_B3 != 0) { + peep = &(g_sprite_list[vehicle->peep].peep); + ride->race_winner = peep->sprite_index; + ride->var_14D |= 12; + } + + // Race is over + ride->lifecycle_flags &= ~RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING; + ride->station_depart[stationIndex] &= ~STATION_DEPART_FLAG; + return; + } + } + + // Continue racing + ride->station_depart[stationIndex] |= STATION_DEPART_FLAG; + } else { + // Check if all vehicles are ready to go + for (i = 0; i < ride->num_vehicles; i++) { + vehicle = &(g_sprite_list[ride->vehicles[i]].vehicle); + if (vehicle->status != VEHICLE_STATUS_WAITING_TO_DEPART && vehicle->status != VEHICLE_STATUS_DEPARTING) { + ride->station_depart[stationIndex] &= ~STATION_DEPART_FLAG; + return; + } + } + + // Begin the race + ride_race_init_vehicle_speeds(ride); + ride->lifecycle_flags |= RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING; + ride->station_depart[stationIndex] |= STATION_DEPART_FLAG; + ride->var_14D |= 12; + } +} + +/** + * + * rct2: 0x006AC988 + * set the speed of the go kart type vehicle at the start to a random value or alter if peep name is an easter egg + * @param ride (esi) + */ +static void ride_race_init_vehicle_speeds(rct_ride *ride) +{ + rct_ride_type *rideEntry; + rct_vehicle *vehicle; + uint8 *unk; + int i; + + for (i = 0; i < ride->num_vehicles; i++) { + vehicle = &g_sprite_list[ride->vehicles[i]].vehicle; + vehicle->var_48 &= ~(1 << 6); + + rideEntry = GET_RIDE_ENTRY(vehicle->var_D6); + unk = (uint8*)((int)rideEntry + (vehicle->var_31 * 0x65)); + + vehicle->speed = (scenario_rand() & 16) - 8 + RCT2_GLOBAL(unk + 0x76, uint8); + + if (vehicle->var_B3) { + rct_peep *peep = &g_sprite_list[vehicle->peep].peep; + + switch (peep_get_easteregg_name_id(peep)) { + case EASTEREGG_PEEP_NAME_MICHAEL_SCHUMACHER: + vehicle->speed += 35; + break; + case EASTEREGG_PEEP_NAME_JACQUES_VILLENEUVE: + vehicle->speed += 25; + break; + case EASTEREGG_PEEP_NAME_DAMON_HILL: + vehicle->speed += 55; + break; + case EASTEREGG_PEEP_NAME_CHRIS_SAWYER: + vehicle->speed += 14; + break; + case EASTEREGG_PEEP_NAME_MR_BEAN: + vehicle->speed = 9; + break; + } + } + } +} + +/** + * + * rct2: 0x006AC2C7 + */ +static void ride_invalidate_station_start(rct_ride *ride, int stationIndex, int dl) +{ + int x, y; + rct_map_element *mapElement; + + x = (ride->station_starts[stationIndex] & 0xFF) * 32; + y = (ride->station_starts[stationIndex] >> 8) * 32; + mapElement = ride_get_station_start_track_element(ride, stationIndex); + + mapElement->properties.track.sequence &= 0x7F; + if (dl != 0) + mapElement->properties.track.sequence |= 0x80; + + // Invalidate map tile + map_invalidate_tile(x, y, mapElement->base_height * 8, mapElement->clearance_height * 8); +} + +rct_map_element *ride_get_station_start_track_element(rct_ride *ride, int stationIndex) +{ + int x, y, z; + rct_map_element *mapElement; + + x = ride->station_starts[stationIndex] & 0xFF; + y = ride->station_starts[stationIndex] >> 8; + z = ride->station_heights[stationIndex]; + + // Get first element of the tile + mapElement = TILE_MAP_ELEMENT_POINTER(y * 256 + x); + + // Find the station track element + do { + if ((mapElement->type & MAP_ELEMENT_TYPE_MASK) == MAP_ELEMENT_TYPE_TRACK && z == mapElement->base_height) + return mapElement; + + mapElement++; + } while (!((mapElement - 1)->flags & MAP_ELEMENT_FLAG_LAST_TILE)); + + return NULL; +} \ No newline at end of file diff --git a/src/ride/station.h b/src/ride/station.h new file mode 100644 index 0000000000..17ed09e9be --- /dev/null +++ b/src/ride/station.h @@ -0,0 +1,31 @@ +/***************************************************************************** + * Copyright (c) 2014 Ted John + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * This file is part of OpenRCT2. + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *****************************************************************************/ + +#ifndef _RIDE_STATION_H_ +#define _RIDE_STATION_H_ + +#include "../common.h" +#include "../world/map.h" +#include "ride.h" + +void ride_update_station(rct_ride *ride, int stationIndex); +rct_map_element *ride_get_station_start_track_element(rct_ride *ride, int stationIndex); + +#endif \ No newline at end of file diff --git a/src/ride/vehicle.h b/src/ride/vehicle.h index 4f7f0db47c..9e7b0c656b 100644 --- a/src/ride/vehicle.h +++ b/src/ride/vehicle.h @@ -76,7 +76,10 @@ typedef struct { uint8 pad_C3[0x09]; uint8 var_CC; uint8 var_CD; - uint8 var_CE; + union { + uint8 var_CE; + uint8 num_laps; // 0xCE + }; uint8 pad_CF[0x07]; uint8 var_D6; } rct_vehicle; diff --git a/src/scenario.c b/src/scenario.c index 1a81cbe709..829f69c32e 100644 --- a/src/scenario.c +++ b/src/scenario.c @@ -642,7 +642,7 @@ void scenario_update() * * rct2: 0x006E37D2 */ -int scenario_rand() +unsigned int scenario_rand() { int eax = RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_SRAND_0, uint32); RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_SRAND_0, uint32) += ror32(RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_SRAND_1, uint32) ^ 0x1234567F, 7); diff --git a/src/scenario.h b/src/scenario.h index e969e8b34d..221a673a22 100644 --- a/src/scenario.h +++ b/src/scenario.h @@ -407,6 +407,6 @@ int scenario_load(const char *path); int scenario_load_and_play(const rct_scenario_basic *scenario); int scenario_load_and_play_from_path(const char *path); void scenario_update(); -int scenario_rand(); +unsigned int scenario_rand(); #endif diff --git a/src/util/util.c b/src/util/util.c index b17de76cee..3ca951960c 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -51,4 +51,15 @@ long fsize(FILE *fp) fseek(fp, originalPosition, SEEK_SET); return size; +} + +int bitscanforward(int source) +{ + int i; + + for (i = 0; i < 32; i++) + if (source & (1 << i)) + return i; + + return -1; } \ No newline at end of file diff --git a/src/util/util.h b/src/util/util.h index a5f03b67e0..bef5f0adc3 100644 --- a/src/util/util.h +++ b/src/util/util.h @@ -29,4 +29,6 @@ int mph_to_kmph(int mph); long fsize(FILE *fp); +int bitscanforward(int source); + #endif diff --git a/src/windows/guest.c b/src/windows/guest.c index 4baa96add5..842a936510 100644 --- a/src/windows/guest.c +++ b/src/windows/guest.c @@ -627,7 +627,7 @@ void window_guest_overview_mouse_up(){ RCT2_CALLPROC_X(0x0069E9D3, 0x8000, 0, peep->y, peep->z, (int)peep, 0, 0); RCT2_CALLPROC_X(0x0069A409, 0, 0, 0, 0, (int)peep, 0, 0); peep->state = 9; - peep->pad_2C = 0; + peep->var_2C = 0; RCT2_CALLPROC_X(0x0069A42F, 0, 0, 0, 0, (int)peep, 0, 0); break; case WIDX_RENAME: @@ -1204,7 +1204,7 @@ void window_guest_overview_tool_down(){ int dest_z = ((uint8*)edx)[2] * 8 + 16; - if (sub_664F72(tile_x, tile_y, dest_z)){ + if (!sub_664F72(tile_x, tile_y, dest_z)){ window_error_open(0x785,-1); return; } diff --git a/src/windows/staff.c b/src/windows/staff.c index a22ad15bab..c6a8e2141f 100644 --- a/src/windows/staff.c +++ b/src/windows/staff.c @@ -1124,7 +1124,7 @@ void window_staff_overview_tool_down(){ int dest_z = ((uint8*)edx)[2] * 8 + 16; - if (sub_664F72(tile_x, tile_y, dest_z)){ + if (!sub_664F72(tile_x, tile_y, dest_z)){ window_error_open(0x785, -1); return; } diff --git a/src/world/map.c b/src/world/map.c index 11b22e7763..b1da00915b 100644 --- a/src/world/map.c +++ b/src/world/map.c @@ -437,27 +437,56 @@ static void sub_6A87BB(int x, int y) RCT2_CALLPROC_X(0x006A87BB, x, 0, y, 0, 0, 0, 0); } -/* rct2: 0x664F72 */ -int sub_664F72(int x, int y, int z){ - if (x > 0x1FFF || y > 0x1FFF){ - RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TEXT, uint16) = 0x6C1; +/** + * + * rct2: 0x00664F72 + */ +int sub_664F72(int x, int y, int z) +{ + rct_map_element *mapElement; + + if (x < (256 * 32) && y < (256 * 32)) { + mapElement = map_get_surface_element_at(x / 32, y / 32); + if (mapElement->properties.surface.ownership & 0x20) + return 1; + + if (mapElement->properties.surface.ownership & 0x10) { + z /= 8; + if (z < mapElement->base_height || z - 2 > mapElement->base_height) + return 1; + } + } + + RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TEXT, uint16) = 1729; + return 0; +} + +/** + * + * rct2: 0x00664F2C + */ +int map_is_location_in_park(int x, int y) +{ + rct_map_element *mapElement; + + if (x < (256 * 32) && y < (256 * 32)) { + mapElement = map_get_surface_element_at(x / 32, y / 32); + if (mapElement->properties.surface.ownership & 0x20) return 1; } - rct_map_element* map_element = map_get_surface_element_at(x / 32, y / 32); - if (map_element->properties.surface.ownership & 0x20) return 0; - if (!(map_element->properties.surface.ownership & 0x10)){ - RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TEXT, uint16) = 0x6C1; - return 1; - } + RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TEXT, uint16) = 1729; + return 0; +} - z >>= 3; - if ((z & 0xFF) < map_element->base_height)return 0; - z = (z & 0xFF) - 2; - if (z > map_element->base_height)return 0; - - RCT2_GLOBAL(RCT2_ADDRESS_GAME_COMMAND_ERROR_TEXT, uint16) = 0x6C1; - return 1; +/** + * + * rct2: 0x006ECB60 + * NOTE: x, y and z are in pixels, not tile units + */ +void map_invalidate_tile(int x, int y, int zLow, int zHigh) +{ + RCT2_CALLPROC_X(0x006ECB60, x, 0, y, 0, zHigh, zLow, 0); } /** diff --git a/src/world/map.h b/src/world/map.h index 113d69d2d8..4ff0115588 100644 --- a/src/world/map.h +++ b/src/world/map.h @@ -206,6 +206,8 @@ int map_coord_is_connected(uint16 coordinate, uint8 height, uint8 face_direction void map_invalidate_animations(); void sub_6A876D(); int sub_664F72(int x, int y, int z); +int map_is_location_in_park(int x, int y); +void map_invalidate_tile(int x, int y, int zLow, int zHigh); void fountain_update_all(); diff --git a/src/world/park.c b/src/world/park.c index 1cf7dfb678..9130a3fb64 100644 --- a/src/world/park.c +++ b/src/world/park.c @@ -508,7 +508,7 @@ static rct_peep *park_generate_new_guest_due_to_campaign(int campaign) static void park_generate_new_guests() { // Generate a new guest for some probability - if ((scenario_rand() & 0xFFFF) < _guestGenerationProbability) { + if ((int)(scenario_rand() & 0xFFFF) < _guestGenerationProbability) { int difficultGeneration = (RCT2_GLOBAL(RCT2_ADDRESS_PARK_FLAGS, uint32) & PARK_FLAGS_DIFFICULT_GUEST_GENERATION) != 0; if (!difficultGeneration || _suggestedGuestMaximum + 150 >= RCT2_GLOBAL(RCT2_ADDRESS_GUESTS_IN_PARK, uint16)) park_generate_new_guest(); @@ -519,7 +519,7 @@ static void park_generate_new_guests() for (campaign = 0; campaign < ADVERTISING_CAMPAIGN_COUNT; campaign++) { if (RCT2_ADDRESS(0x01358102, uint8)[campaign] != 0) { // Random chance of guest generation - if ((scenario_rand() & 0xFFFF) < marketing_get_campaign_guest_generation_probability(campaign)) + if ((int)(scenario_rand() & 0xFFFF) < marketing_get_campaign_guest_generation_probability(campaign)) park_generate_new_guest_due_to_campaign(campaign); } }