diff --git a/data/language/en-GB.txt b/data/language/en-GB.txt index 56d0323e58..ebd2c98aad 100644 --- a/data/language/en-GB.txt +++ b/data/language/en-GB.txt @@ -3770,6 +3770,9 @@ STR_6319 :{WINDOW_COLOUR_2}Block Brake Closed STR_6320 :{WINDOW_COLOUR_2}Indestructible STR_6321 :{WINDOW_COLOUR_2}Addition is broken STR_6322 :{WINDOW_COLOUR_2}Sprite Id: {BLACK}{INT32} +STR_6323 :Simulating +STR_6324 :Simulate +STR_6325 :{SMALLFONT}{BLACK}Simulate ride/attraction ############# # Scenarios # diff --git a/distribution/changelog.txt b/distribution/changelog.txt index b1877ade72..dcd7fc082b 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,5 +1,6 @@ 0.2.2+ (in development) ------------------------------------------------------------------------ +- Feature: [#485] Rides can now be simulated with ghost trains during construction. - Feature: [#2339] Find local servers automatically when fetching servers. - Feature: [#7296] Allow assigning a keyboard shortcut for the scenery picker. - Feature: [#8029] Add the Hungarian Forint (HUF) to the list of available currencies. diff --git a/src/openrct2-ui/windows/Ride.cpp b/src/openrct2-ui/windows/Ride.cpp index c0d3f92e61..61ef2f47a3 100644 --- a/src/openrct2-ui/windows/Ride.cpp +++ b/src/openrct2-ui/windows/Ride.cpp @@ -98,6 +98,7 @@ enum { WIDX_LOCATE, WIDX_DEMOLISH, WIDX_CLOSE_LIGHT, + WIDX_SIMULATE_LIGHT, WIDX_TEST_LIGHT, WIDX_OPEN_LIGHT, WIDX_RIDE_TYPE, @@ -231,6 +232,7 @@ static rct_widget window_ride_main_widgets[] = { { WWT_FLATBTN, 1, 291, 314, 118, 141, SPR_LOCATE, STR_LOCATE_SUBJECT_TIP }, { WWT_FLATBTN, 1, 291, 314, 142, 165, SPR_DEMOLISH, STR_DEMOLISH_RIDE_TIP }, { WWT_IMGBTN, 1, 296, 309, 48, 61, SPR_G2_RCT1_CLOSE_BUTTON_0, STR_CLOSE_RIDE_TIP }, + { WWT_IMGBTN, 1, 296, 309, 62, 75, SPR_G2_RCT1_TEST_BUTTON_0, STR_SIMULATE_RIDE_TIP }, { WWT_IMGBTN, 1, 296, 309, 62, 75, SPR_G2_RCT1_TEST_BUTTON_0, STR_TEST_RIDE_TIP }, { WWT_IMGBTN, 1, 296, 309, 76, 89, SPR_G2_RCT1_OPEN_BUTTON_0, STR_OPEN_RIDE_TIP }, { WWT_DROPDOWN, 1, 3, 307, 180, 191, STR_ARG_6_STRINGID, STR_NONE }, @@ -397,6 +399,7 @@ static constexpr const uint64_t window_ride_page_enabled_widgets[] = { (1ULL << WIDX_LOCATE) | (1ULL << WIDX_DEMOLISH) | (1ULL << WIDX_CLOSE_LIGHT) | + (1ULL << WIDX_SIMULATE_LIGHT) | (1ULL << WIDX_TEST_LIGHT) | (1ULL << WIDX_OPEN_LIGHT) | (1ULL << WIDX_RIDE_TYPE) | @@ -2024,6 +2027,7 @@ static void window_ride_main_mouseup(rct_window* w, rct_widgetindex widgetIndex) context_open_detail_window(WD_DEMOLISH_RIDE, w->number); break; case WIDX_CLOSE_LIGHT: + case WIDX_SIMULATE_LIGHT: case WIDX_TEST_LIGHT: case WIDX_OPEN_LIGHT: switch (widgetIndex) @@ -2032,6 +2036,9 @@ static void window_ride_main_mouseup(rct_window* w, rct_widgetindex widgetIndex) case WIDX_CLOSE_LIGHT: status = RIDE_STATUS_CLOSED; break; + case WIDX_SIMULATE_LIGHT: + status = RIDE_STATUS_SIMULATING; + break; case WIDX_TEST_LIGHT: status = RIDE_STATUS_TESTING; break; @@ -2060,7 +2067,7 @@ static void window_ride_main_resize(rct_window* w) int32_t minHeight = 180 + offset; if (theme_get_flags() & UITHEME_FLAG_USE_LIGHTS_RIDE) minHeight = 200 + offset + RCT1_LIGHT_OFFSET - - (ride_type_has_flag(get_ride(w->number)->type, RIDE_TYPE_FLAG_NO_TEST_MODE) ? 14 : 0); + - (ride_type_has_flag(get_ride(w->number)->type, RIDE_TYPE_FLAG_NO_TEST_MODE) ? 14 * 2 : 0); window_set_resize(w, 316, minHeight, 500, 450); window_ride_init_viewport(w); } @@ -2130,7 +2137,7 @@ static void window_ride_show_view_dropdown(rct_window* w, rct_widget* widget) static void window_ride_show_open_dropdown(rct_window* w, rct_widget* widget) { Ride* ride; - int32_t numItems, highlightedIndex = 0, checkedIndex; + int32_t numItems, highlightedIndex = 0; ride = get_ride(w->number); @@ -2141,6 +2148,10 @@ static void window_ride_show_open_dropdown(rct_window* w, rct_widget* widget) if (!ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_NO_TEST_MODE)) { + gDropdownItemsFormat[numItems] = STR_DROPDOWN_MENU_LABEL; + gDropdownItemsArgs[numItems] = STR_SIMULATE_RIDE; + numItems++; + gDropdownItemsFormat[numItems] = STR_DROPDOWN_MENU_LABEL; gDropdownItemsArgs[numItems] = STR_TEST_RIDE; numItems++; @@ -2153,47 +2164,52 @@ static void window_ride_show_open_dropdown(rct_window* w, rct_widget* widget) window_dropdown_show_text( w->x + widget->left, w->y + widget->top, widget->bottom - widget->top + 1, w->colours[1], 0, numItems); - checkedIndex = ride->status; + auto checkedIndex = -1; switch (ride->status) { case RIDE_STATUS_CLOSED: + checkedIndex = 0; highlightedIndex = 0; if ((ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED) || (ride->lifecycle_flags & RIDE_LIFECYCLE_HAS_STALLED_VEHICLE)) break; + highlightedIndex = 3; + if (!ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_NO_TEST_MODE)) + if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED)) + highlightedIndex = 2; + break; + case RIDE_STATUS_SIMULATING: + checkedIndex = 1; highlightedIndex = 2; - if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_NO_TEST_MODE)) - break; - if (ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED) - break; - - highlightedIndex = 1; + if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED)) + highlightedIndex = 0; break; case RIDE_STATUS_TESTING: - highlightedIndex = 2; - if (ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED) - break; - - highlightedIndex = 0; + checkedIndex = 2; + highlightedIndex = 3; + if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED)) + highlightedIndex = 3; break; case RIDE_STATUS_OPEN: + checkedIndex = 3; highlightedIndex = 0; break; } - if (checkedIndex != RIDE_STATUS_CLOSED) - checkedIndex = 3 - checkedIndex; - if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_NO_TEST_MODE)) { - if (checkedIndex != 0) - checkedIndex--; - if (highlightedIndex != 0) - highlightedIndex--; + // Open item is now index 1 + if (checkedIndex == 3) + checkedIndex = 1; + if (highlightedIndex == 3) + highlightedIndex = 1; } - dropdown_set_checked(checkedIndex, true); + if (checkedIndex != -1) + { + dropdown_set_checked(checkedIndex, true); + } gDropdownDefaultIndex = highlightedIndex; } @@ -2412,7 +2428,7 @@ static void window_ride_main_dropdown(rct_window* w, rct_widgetindex widgetIndex ride = get_ride(w->number); if (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_NO_TEST_MODE) && dropdownIndex != 0) - dropdownIndex++; + dropdownIndex += 2; switch (dropdownIndex) { @@ -2420,9 +2436,12 @@ static void window_ride_main_dropdown(rct_window* w, rct_widgetindex widgetIndex status = RIDE_STATUS_CLOSED; break; case 1: - status = RIDE_STATUS_TESTING; + status = RIDE_STATUS_SIMULATING; break; case 2: + status = RIDE_STATUS_TESTING; + break; + case 3: status = RIDE_STATUS_OPEN; break; } @@ -2533,11 +2552,14 @@ static void window_ride_main_invalidate(rct_window* w) SPR_CLOSED, SPR_OPEN, SPR_TESTING, + SPR_TESTING | (COLOUR_WHITE << 19 | COLOUR_WHITE << 24 | IMAGE_TYPE_REMAP), }; window_ride_main_widgets[WIDX_OPEN].image = spriteIds[ride->status]; window_ride_main_widgets[WIDX_CLOSE_LIGHT].image = SPR_G2_RCT1_CLOSE_BUTTON_0 + (ride->status == RIDE_STATUS_CLOSED) * 2 + widget_is_pressed(w, WIDX_CLOSE_LIGHT); + window_ride_main_widgets[WIDX_SIMULATE_LIGHT].image = SPR_G2_RCT1_TEST_BUTTON_0 + + (ride->status == RIDE_STATUS_SIMULATING) * 2 + widget_is_pressed(w, WIDX_SIMULATE_LIGHT); window_ride_main_widgets[WIDX_TEST_LIGHT].image = SPR_G2_RCT1_TEST_BUTTON_0 + (ride->status == RIDE_STATUS_TESTING) * 2 + widget_is_pressed(w, WIDX_TEST_LIGHT); window_ride_main_widgets[WIDX_OPEN_LIGHT].image = SPR_G2_RCT1_OPEN_BUTTON_0 + (ride->status == RIDE_STATUS_OPEN) * 2 @@ -2580,11 +2602,19 @@ static void window_ride_main_invalidate(rct_window* w) { window_ride_main_widgets[WIDX_OPEN].type = WWT_EMPTY; window_ride_main_widgets[WIDX_CLOSE_LIGHT].type = WWT_IMGBTN; + window_ride_main_widgets[WIDX_SIMULATE_LIGHT].type + = (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_NO_TEST_MODE) ? WWT_EMPTY : WWT_IMGBTN); window_ride_main_widgets[WIDX_TEST_LIGHT].type = (ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_NO_TEST_MODE) ? WWT_EMPTY : WWT_IMGBTN); window_ride_main_widgets[WIDX_OPEN_LIGHT].type = WWT_IMGBTN; height = 62; + if (window_ride_main_widgets[WIDX_SIMULATE_LIGHT].type != WWT_EMPTY) + { + window_ride_main_widgets[WIDX_SIMULATE_LIGHT].top = height; + window_ride_main_widgets[WIDX_SIMULATE_LIGHT].bottom = height + 13; + height += 14; + } if (window_ride_main_widgets[WIDX_TEST_LIGHT].type != WWT_EMPTY) { window_ride_main_widgets[WIDX_TEST_LIGHT].top = height; @@ -2603,6 +2633,7 @@ static void window_ride_main_invalidate(rct_window* w) { window_ride_main_widgets[WIDX_OPEN].type = WWT_FLATBTN; window_ride_main_widgets[WIDX_CLOSE_LIGHT].type = WWT_EMPTY; + window_ride_main_widgets[WIDX_SIMULATE_LIGHT].type = WWT_EMPTY; window_ride_main_widgets[WIDX_TEST_LIGHT].type = WWT_EMPTY; window_ride_main_widgets[WIDX_OPEN_LIGHT].type = WWT_EMPTY; height = 46; diff --git a/src/openrct2-ui/windows/RideConstruction.cpp b/src/openrct2-ui/windows/RideConstruction.cpp index 408d789a8c..e5ed3c16f2 100644 --- a/src/openrct2-ui/windows/RideConstruction.cpp +++ b/src/openrct2-ui/windows/RideConstruction.cpp @@ -2026,7 +2026,7 @@ static void window_ride_construction_update(rct_window* w) // Close construction window if ride is not closed, // editing ride while open will cause many issues until properly handled - if (ride->status != RIDE_STATUS_CLOSED) + if (ride->status != RIDE_STATUS_CLOSED && ride->status != RIDE_STATUS_SIMULATING) { window_close(w); return; diff --git a/src/openrct2/actions/RideDemolishAction.hpp b/src/openrct2/actions/RideDemolishAction.hpp index bca4f79d94..73562abf3b 100644 --- a/src/openrct2/actions/RideDemolishAction.hpp +++ b/src/openrct2/actions/RideDemolishAction.hpp @@ -78,7 +78,7 @@ public: if (_modifyType == RIDE_MODIFY_RENEW) { - if (ride->status != RIDE_STATUS_CLOSED) + if (ride->status != RIDE_STATUS_CLOSED && ride->status != RIDE_STATUS_SIMULATING) { return std::make_unique( GA_ERROR::DISALLOWED, STR_CANT_REFURBISH_RIDE, STR_MUST_BE_CLOSED_FIRST); diff --git a/src/openrct2/actions/RideEntranceExitPlaceAction.hpp b/src/openrct2/actions/RideEntranceExitPlaceAction.hpp index 7c584689d2..83e77d84e1 100644 --- a/src/openrct2/actions/RideEntranceExitPlaceAction.hpp +++ b/src/openrct2/actions/RideEntranceExitPlaceAction.hpp @@ -79,7 +79,7 @@ public: return MakeResult(GA_ERROR::INVALID_PARAMETERS, errorTitle); } - if (ride->status != RIDE_STATUS_CLOSED) + if (ride->status != RIDE_STATUS_CLOSED && ride->status != RIDE_STATUS_SIMULATING) { return MakeResult(GA_ERROR::NOT_CLOSED, errorTitle, STR_MUST_BE_CLOSED_FIRST); } diff --git a/src/openrct2/actions/RideEntranceExitRemoveAction.hpp b/src/openrct2/actions/RideEntranceExitRemoveAction.hpp index c13f1deaa3..88ce3f3536 100644 --- a/src/openrct2/actions/RideEntranceExitRemoveAction.hpp +++ b/src/openrct2/actions/RideEntranceExitRemoveAction.hpp @@ -60,7 +60,7 @@ public: return std::make_unique(GA_ERROR::INVALID_PARAMETERS, STR_NONE); } - if (ride->status != RIDE_STATUS_CLOSED) + if (ride->status != RIDE_STATUS_CLOSED && ride->status != RIDE_STATUS_SIMULATING) { return MakeResult(GA_ERROR::INVALID_PARAMETERS, STR_MUST_BE_CLOSED_FIRST); } diff --git a/src/openrct2/actions/RideSetSetting.hpp b/src/openrct2/actions/RideSetSetting.hpp index ade1fcb5a4..ed560e9053 100644 --- a/src/openrct2/actions/RideSetSetting.hpp +++ b/src/openrct2/actions/RideSetSetting.hpp @@ -82,7 +82,7 @@ public: GA_ERROR::DISALLOWED, STR_CANT_CHANGE_OPERATING_MODE, STR_HAS_BROKEN_DOWN_AND_REQUIRES_FIXING); } - if (ride->status != RIDE_STATUS_CLOSED) + if (ride->status != RIDE_STATUS_CLOSED && ride->status != RIDE_STATUS_SIMULATING) { return MakeResult(GA_ERROR::DISALLOWED, STR_CANT_CHANGE_OPERATING_MODE, STR_MUST_BE_CLOSED_FIRST); } diff --git a/src/openrct2/actions/RideSetStatus.hpp b/src/openrct2/actions/RideSetStatus.hpp index 5dff42ab41..3e5adc6eb8 100644 --- a/src/openrct2/actions/RideSetStatus.hpp +++ b/src/openrct2/actions/RideSetStatus.hpp @@ -73,9 +73,9 @@ public: if (_status != ride->status) { - if (_status == RIDE_STATUS_TESTING) + if (_status == RIDE_STATUS_TESTING || _status == RIDE_STATUS_SIMULATING) { - if (!ride_is_valid_for_test(ride, _status == RIDE_STATUS_OPEN, 0)) + if (!ride_is_valid_for_test(ride, _status, 0)) { res->Error = GA_ERROR::UNKNOWN; res->ErrorMessage = gGameCommandErrorText; @@ -123,7 +123,7 @@ public: switch (_status) { case RIDE_STATUS_CLOSED: - if (ride->status == _status) + if (ride->status == _status || ride->status == RIDE_STATUS_SIMULATING) { if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN)) { @@ -139,6 +139,29 @@ public: ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST; window_invalidate_by_number(WC_RIDE, _rideIndex); break; + case RIDE_STATUS_SIMULATING: + { + ride->lifecycle_flags &= ~RIDE_LIFECYCLE_CRASHED; + ride_clear_for_construction(ride); + ride_remove_peeps(ride); + + if (!ride_is_valid_for_test(ride, _status, 1)) + { + res->Error = GA_ERROR::UNKNOWN; + res->ErrorMessage = gGameCommandErrorText; + return res; + } + + ride->status = _status; + ride->lifecycle_flags &= ~RIDE_LIFECYCLE_PASS_STATION_NO_STOPPING; + ride->race_winner = SPRITE_INDEX_NULL; + ride->current_issues = 0; + ride->last_issue_time = 0; + ride_get_measurement(ride, nullptr); + ride->window_invalidate_flags |= RIDE_INVALIDATE_RIDE_MAIN | RIDE_INVALIDATE_RIDE_LIST; + window_invalidate_by_number(WC_RIDE, _rideIndex); + break; + } case RIDE_STATUS_TESTING: case RIDE_STATUS_OPEN: { @@ -147,6 +170,12 @@ public: return res; } + if (ride->status == RIDE_STATUS_SIMULATING) + { + ride_clear_for_construction(ride); + ride_remove_peeps(ride); + } + // Fix #3183: Make sure we close the construction window so the ride finishes any editing code before opening // otherwise vehicles get added to the ride incorrectly (such as to a ghost station) rct_window* constructionWindow = window_find_by_number(WC_RIDE_CONSTRUCTION, _rideIndex); @@ -157,7 +186,7 @@ public: if (_status == RIDE_STATUS_TESTING) { - if (!ride_is_valid_for_test(ride, _status == RIDE_STATUS_OPEN, 1)) + if (!ride_is_valid_for_test(ride, _status, 1)) { res->Error = GA_ERROR::UNKNOWN; res->ErrorMessage = gGameCommandErrorText; diff --git a/src/openrct2/actions/RideSetVehiclesAction.hpp b/src/openrct2/actions/RideSetVehiclesAction.hpp index 04a8e3b04a..69b89c02ac 100644 --- a/src/openrct2/actions/RideSetVehiclesAction.hpp +++ b/src/openrct2/actions/RideSetVehiclesAction.hpp @@ -92,7 +92,7 @@ public: return std::make_unique(GA_ERROR::BROKEN, errTitle, STR_HAS_BROKEN_DOWN_AND_REQUIRES_FIXING); } - if (ride->status != RIDE_STATUS_CLOSED) + if (ride->status != RIDE_STATUS_CLOSED && ride->status != RIDE_STATUS_SIMULATING) { return std::make_unique(GA_ERROR::NOT_CLOSED, errTitle, STR_MUST_BE_CLOSED_FIRST); } diff --git a/src/openrct2/localisation/StringIds.h b/src/openrct2/localisation/StringIds.h index 4e26c2a50b..838aa3f577 100644 --- a/src/openrct2/localisation/StringIds.h +++ b/src/openrct2/localisation/StringIds.h @@ -3959,6 +3959,10 @@ enum STR_DESYNC_REPORT = 6318, + STR_SIMULATING = 6323, + STR_SIMULATE_RIDE = 6324, + STR_SIMULATE_RIDE_TIP = 6325, + // Have to include resource strings (from scenarios and objects) for the time being now that language is partially working STR_COUNT = 32768 }; diff --git a/src/openrct2/paint/Paint.h b/src/openrct2/paint/Paint.h index 6128bc14c1..3d7780b6af 100644 --- a/src/openrct2/paint/Paint.h +++ b/src/openrct2/paint/Paint.h @@ -182,7 +182,7 @@ extern LocationXY8 gClipSelectionA; extern LocationXY8 gClipSelectionB; /** rct2: 0x00993CC4. The white ghost that indicates not-yet-built elements. */ -#define CONSTRUCTION_MARKER (COLOUR_DARK_GREEN << 19 | COLOUR_GREY << 24 | IMAGE_TYPE_REMAP); +#define CONSTRUCTION_MARKER (COLOUR_DARK_GREEN << 19 | COLOUR_GREY << 24 | IMAGE_TYPE_REMAP) extern bool gShowDirtyVisuals; extern bool gPaintBoundingBoxes; extern bool gPaintBlockedTiles; diff --git a/src/openrct2/peep/Guest.cpp b/src/openrct2/peep/Guest.cpp index 2a82a05de6..6aba5ec4c7 100644 --- a/src/openrct2/peep/Guest.cpp +++ b/src/openrct2/peep/Guest.cpp @@ -5404,7 +5404,7 @@ void Guest::UpdateQueuing() return; } Ride* ride = get_ride(current_ride); - if (ride->status == RIDE_STATUS_CLOSED || ride->status == RIDE_STATUS_TESTING) + if (ride->status != RIDE_STATUS_OPEN) { RemoveFromQueue(); SetState(PEEP_STATE_1); diff --git a/src/openrct2/ride/Ride.cpp b/src/openrct2/ride/Ride.cpp index 9345dc4adc..4763cc3198 100644 --- a/src/openrct2/ride/Ride.cpp +++ b/src/openrct2/ride/Ride.cpp @@ -897,7 +897,12 @@ void ride_get_status(const Ride* ride, rct_string_id* formatSecondary, int32_t* return; } - if (ride->status == RIDE_STATUS_TESTING) + else if (ride->status == RIDE_STATUS_SIMULATING) + { + *formatSecondary = STR_SIMULATING; + return; + } + else if (ride->status == RIDE_STATUS_TESTING) { *formatSecondary = STR_TEST_RUN; return; @@ -1024,7 +1029,7 @@ static int32_t ride_check_if_construction_allowed(Ride* ride) return 0; } - if (ride->status != RIDE_STATUS_CLOSED) + if (ride->status != RIDE_STATUS_CLOSED && ride->status != RIDE_STATUS_SIMULATING) { set_format_arg(6, rct_string_id, ride->name); set_format_arg(8, uint32_t, ride->name_arguments); @@ -1985,7 +1990,10 @@ int32_t ride_modify(CoordsXYE* input) } // Stop the ride again to clear all vehicles and peeps (compatible with network games) - ride_set_status(ride, RIDE_STATUS_CLOSED); + if (ride->status != RIDE_STATUS_SIMULATING) + { + ride_set_status(ride, RIDE_STATUS_CLOSED); + } // Check if element is a station entrance or exit if (tileElement.element->GetType() == TILE_ELEMENT_TYPE_ENTRANCE) @@ -2192,24 +2200,10 @@ void Ride::Update() ride_inspection_update(this); - if (status == RIDE_STATUS_TESTING && gConfigGeneral.no_test_crashes) + // If ride is simulating but crashed, reset the vehicles + if (status == RIDE_STATUS_SIMULATING && (lifecycle_flags & RIDE_LIFECYCLE_CRASHED)) { - for (int32_t i = 0; i < num_vehicles; i++) - { - uint16_t spriteIndex = vehicles[i]; - if (spriteIndex == SPRITE_INDEX_NULL) - continue; - - rct_vehicle* vehicle = GET_VEHICLE(spriteIndex); - - if (vehicle->status == VEHICLE_STATUS_CRASHED || vehicle->status == VEHICLE_STATUS_CRASHING) - { - ride_set_status(this, RIDE_STATUS_CLOSED); - ride_set_status(this, RIDE_STATUS_CLOSED); - ride_set_status(this, RIDE_STATUS_TESTING); - break; - } - } + ride_set_status(this, RIDE_STATUS_SIMULATING); } } @@ -5396,7 +5390,7 @@ static TileElement* loc_6B4F6B(ride_id_t rideIndex, int32_t x, int32_t y) return nullptr; } -int32_t ride_is_valid_for_test(Ride* ride, int32_t goingToBeOpen, int32_t isApplying) +int32_t ride_is_valid_for_test(Ride* ride, int32_t status, int32_t isApplying) { int32_t stationIndex; CoordsXYE trackElement, problematicTrackElement = {}; @@ -5407,7 +5401,10 @@ int32_t ride_is_valid_for_test(Ride* ride, int32_t goingToBeOpen, int32_t isAppl return 0; } - window_close_by_number(WC_RIDE_CONSTRUCTION, ride->id); + if (status != RIDE_STATUS_SIMULATING) + { + window_close_by_number(WC_RIDE_CONSTRUCTION, ride->id); + } stationIndex = ride_mode_check_station_present(ride); if (stationIndex == -1) @@ -5422,7 +5419,7 @@ int32_t ride_is_valid_for_test(Ride* ride, int32_t goingToBeOpen, int32_t isAppl return 0; } - if (goingToBeOpen && isApplying) + if (status == RIDE_STATUS_OPEN && isApplying) { sub_6B5952(ride); ride->lifecycle_flags |= RIDE_LIFECYCLE_EVER_BEEN_OPENED; @@ -5443,7 +5440,7 @@ int32_t ride_is_valid_for_test(Ride* ride, int32_t goingToBeOpen, int32_t isAppl || ride->mode == RIDE_MODE_CONTINUOUS_CIRCUIT_BLOCK_SECTIONED || ride->mode == RIDE_MODE_POWERED_LAUNCH_BLOCK_SECTIONED) { if (ride_find_track_gap(ride, &trackElement, &problematicTrackElement) - && (!gConfigGeneral.test_unfinished_tracks || ride->mode == RIDE_MODE_CONTINUOUS_CIRCUIT_BLOCK_SECTIONED + && (status != RIDE_STATUS_SIMULATING || ride->mode == RIDE_MODE_CONTINUOUS_CIRCUIT_BLOCK_SECTIONED || ride->mode == RIDE_MODE_POWERED_LAUNCH_BLOCK_SECTIONED)) { gGameCommandErrorText = STR_TRACK_IS_NOT_A_COMPLETE_CIRCUIT; diff --git a/src/openrct2/ride/Ride.h b/src/openrct2/ride/Ride.h index 34bbbc86c3..4b122ba6ad 100644 --- a/src/openrct2/ride/Ride.h +++ b/src/openrct2/ride/Ride.h @@ -631,7 +631,8 @@ enum { RIDE_STATUS_CLOSED, RIDE_STATUS_OPEN, - RIDE_STATUS_TESTING + RIDE_STATUS_TESTING, + RIDE_STATUS_SIMULATING, }; enum @@ -1074,7 +1075,7 @@ rct_ride_measurement* ride_get_measurement(Ride* ride, rct_string_id* message); void ride_breakdown_add_news_item(Ride* ride); Peep* ride_find_closest_mechanic(Ride* ride, int32_t forInspection); int32_t ride_is_valid_for_open(Ride* ride, int32_t goingToBeOpen, int32_t isApplying); -int32_t ride_is_valid_for_test(Ride* ride, int32_t goingToBeOpen, int32_t isApplying); +int32_t ride_is_valid_for_test(Ride* ride, int32_t status, int32_t isApplying); int32_t ride_initialise_construction_window(Ride* ride); void ride_construction_invalidate_current_track(); int32_t sub_6C683D( diff --git a/src/openrct2/ride/Vehicle.cpp b/src/openrct2/ride/Vehicle.cpp index c6f812854f..678d4fcfd4 100644 --- a/src/openrct2/ride/Vehicle.cpp +++ b/src/openrct2/ride/Vehicle.cpp @@ -3520,6 +3520,15 @@ static void vehicle_check_if_missing(rct_vehicle* vehicle) news_item_add_to_queue(NEWS_ITEM_RIDE, STR_NEWS_VEHICLE_HAS_STALLED, vehicle->ride); } +static void vehicle_simulate_crash(rct_vehicle* vehicle) +{ + auto ride = get_ride(vehicle->ride); + if (ride != nullptr) + { + ride->lifecycle_flags |= RIDE_LIFECYCLE_CRASHED; + } +} + /** * Setup function for a vehicle colliding with * another vehicle. @@ -3528,10 +3537,16 @@ static void vehicle_check_if_missing(rct_vehicle* vehicle) */ static void vehicle_update_collision_setup(rct_vehicle* vehicle) { + auto ride = get_ride(vehicle->ride); + if (ride != nullptr && ride->status == RIDE_STATUS_SIMULATING) + { + vehicle_simulate_crash(vehicle); + return; + } + vehicle->status = VEHICLE_STATUS_CRASHED; vehicle_invalidate_window(vehicle); - Ride* ride = get_ride(vehicle->ride); if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED)) { auto frontVehicle = vehicle->GetHead(); @@ -3603,6 +3618,13 @@ static constexpr const LocationXY16 stru_9A3AC4[] = { */ static void vehicle_update_crash_setup(rct_vehicle* vehicle) { + auto ride = get_ride(vehicle->ride); + if (ride != nullptr && ride->status == RIDE_STATUS_SIMULATING) + { + vehicle_simulate_crash(vehicle); + return; + } + vehicle->status = VEHICLE_STATUS_CRASHING; vehicle_invalidate_window(vehicle); @@ -5266,10 +5288,16 @@ static void vehicle_kill_all_passengers(rct_vehicle* vehicle) static void vehicle_crash_on_land(rct_vehicle* vehicle) { + auto ride = get_ride(vehicle->ride); + if (ride != nullptr && ride->status == RIDE_STATUS_SIMULATING) + { + vehicle_simulate_crash(vehicle); + return; + } + vehicle->status = VEHICLE_STATUS_CRASHED; vehicle_invalidate_window(vehicle); - Ride* ride = get_ride(vehicle->ride); if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED)) { auto frontVehicle = vehicle->GetHead(); @@ -5320,10 +5348,16 @@ static void vehicle_crash_on_land(rct_vehicle* vehicle) static void vehicle_crash_on_water(rct_vehicle* vehicle) { + auto ride = get_ride(vehicle->ride); + if (ride != nullptr && ride->status == RIDE_STATUS_SIMULATING) + { + vehicle_simulate_crash(vehicle); + return; + } + vehicle->status = VEHICLE_STATUS_CRASHED; vehicle_invalidate_window(vehicle); - Ride* ride = get_ride(vehicle->ride); if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED)) { auto frontVehicle = vehicle->GetHead(); diff --git a/src/openrct2/ride/VehiclePaint.cpp b/src/openrct2/ride/VehiclePaint.cpp index e1d91f78ac..e7d40507d4 100644 --- a/src/openrct2/ride/VehiclePaint.cpp +++ b/src/openrct2/ride/VehiclePaint.cpp @@ -899,6 +899,13 @@ static void vehicle_sprite_paint( } vehicle_boundbox bb = VehicleBoundboxes[vehicleEntry->draw_order][ecx]; + bool isGhost = false; + auto ride = get_ride(vehicle->ride); + if (ride != nullptr && ride->status == RIDE_STATUS_SIMULATING) + { + isGhost = true; + } + if (vehicleEntry->flags & VEHICLE_ENTRY_FLAG_SPINNING_ADDITIONAL_FRAMES) { baseImage_id += (vehicle->spin_sprite / 8) & 31; @@ -909,6 +916,12 @@ static void vehicle_sprite_paint( } int32_t image_id = baseImage_id | (vehicle->colours.body_colour << 19) | (vehicle->colours.trim_colour << 24) | IMAGE_TYPE_REMAP_2_PLUS; + + if (isGhost) + { + image_id &= 0x7FFFF; + image_id |= CONSTRUCTION_MARKER; + } paint_struct* ps = sub_98197C( session, image_id, 0, 0, bb.length_x, bb.length_y, bb.length_z, z, bb.offset_x, bb.offset_y, bb.offset_z + z); if (ps != nullptr) @@ -930,6 +943,13 @@ static void vehicle_sprite_paint( { image_id += (vehicleEntry->no_vehicle_images * vehicle->animation_frame); } + + if (isGhost) + { + image_id &= 0x7FFFF; + image_id |= CONSTRUCTION_MARKER; + } + sub_98199C( session, image_id, 0, 0, bb.length_x, bb.length_y, bb.length_z, z, bb.offset_x, bb.offset_y, bb.offset_z + z);