/***************************************************************************** * Copyright (c) 2014-2022 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #include "../interface/Theme.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace OpenRCT2; using namespace OpenRCT2::TrackMetaData; static constexpr const StringId WINDOW_TITLE = STR_RIDE_WINDOW_TITLE; static constexpr const int32_t WH = 207; static constexpr const int32_t WW = 316; static void PopulateVehicleTypeDropdown(Ride* ride, bool forceRefresh = false); enum { WINDOW_RIDE_PAGE_MAIN, WINDOW_RIDE_PAGE_VEHICLE, WINDOW_RIDE_PAGE_OPERATING, WINDOW_RIDE_PAGE_MAINTENANCE, WINDOW_RIDE_PAGE_COLOUR, WINDOW_RIDE_PAGE_MUSIC, WINDOW_RIDE_PAGE_MEASUREMENTS, WINDOW_RIDE_PAGE_GRAPHS, WINDOW_RIDE_PAGE_INCOME, WINDOW_RIDE_PAGE_CUSTOMER, WINDOW_RIDE_PAGE_COUNT }; #pragma region Widgets // clang-format off enum { WIDX_BACKGROUND, WIDX_TITLE, WIDX_CLOSE, WIDX_PAGE_BACKGROUND, WIDX_TAB_1, WIDX_TAB_2, WIDX_TAB_3, WIDX_TAB_4, WIDX_TAB_5, WIDX_TAB_6, WIDX_TAB_7, WIDX_TAB_8, WIDX_TAB_9, WIDX_TAB_10, WIDX_VIEWPORT = 14, WIDX_VIEW, WIDX_VIEW_DROPDOWN, WIDX_STATUS, WIDX_OPEN, WIDX_CONSTRUCTION, WIDX_RENAME, WIDX_LOCATE, WIDX_DEMOLISH, WIDX_CLOSE_LIGHT, WIDX_SIMULATE_LIGHT, WIDX_TEST_LIGHT, WIDX_OPEN_LIGHT, WIDX_RIDE_TYPE, WIDX_RIDE_TYPE_DROPDOWN, WIDX_VEHICLE_TYPE = 14, WIDX_VEHICLE_TYPE_DROPDOWN, WIDX_VEHICLE_TRAINS_PREVIEW, WIDX_VEHICLE_TRAINS, WIDX_VEHICLE_TRAINS_INCREASE, WIDX_VEHICLE_TRAINS_DECREASE, WIDX_VEHICLE_CARS_PER_TRAIN, WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE, WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE, WIDX_MODE_TWEAK = 14, WIDX_MODE_TWEAK_INCREASE, WIDX_MODE_TWEAK_DECREASE, WIDX_LIFT_HILL_SPEED, WIDX_LIFT_HILL_SPEED_INCREASE, WIDX_LIFT_HILL_SPEED_DECREASE, WIDX_LOAD_CHECKBOX, WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX, WIDX_MINIMUM_LENGTH_CHECKBOX, WIDX_MINIMUM_LENGTH, WIDX_MINIMUM_LENGTH_INCREASE, WIDX_MINIMUM_LENGTH_DECREASE, WIDX_MAXIMUM_LENGTH_CHECKBOX, WIDX_MAXIMUM_LENGTH, WIDX_MAXIMUM_LENGTH_INCREASE, WIDX_MAXIMUM_LENGTH_DECREASE, WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX, WIDX_MODE_TWEAK_LABEL, WIDX_LIFT_HILL_SPEED_LABEL, WIDX_MODE, WIDX_MODE_DROPDOWN, WIDX_LOAD, WIDX_LOAD_DROPDOWN, WIDX_OPERATE_NUMBER_OF_CIRCUITS_LABEL, WIDX_OPERATE_NUMBER_OF_CIRCUITS, WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE, WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE, WIDX_INSPECTION_INTERVAL = 14, WIDX_INSPECTION_INTERVAL_DROPDOWN, WIDX_LOCATE_MECHANIC, WIDX_REFURBISH_RIDE, WIDX_FORCE_BREAKDOWN, WIDX_TRACK_PREVIEW = 14, WIDX_TRACK_COLOUR_SCHEME, WIDX_TRACK_COLOUR_SCHEME_DROPDOWN, WIDX_TRACK_MAIN_COLOUR, WIDX_TRACK_ADDITIONAL_COLOUR, WIDX_TRACK_SUPPORT_COLOUR, WIDX_MAZE_STYLE, WIDX_MAZE_STYLE_DROPDOWN, WIDX_PAINT_INDIVIDUAL_AREA, WIDX_ENTRANCE_PREVIEW, WIDX_ENTRANCE_STYLE, WIDX_ENTRANCE_STYLE_DROPDOWN, WIDX_VEHICLE_PREVIEW, WIDX_VEHICLE_COLOUR_SCHEME, WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN, WIDX_VEHICLE_COLOUR_INDEX, WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN, WIDX_VEHICLE_BODY_COLOR, WIDX_VEHICLE_TRIM_COLOUR, WIDX_VEHICLE_TERNARY_COLOUR, WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX, WIDX_PLAY_MUSIC = 14, WIDX_MUSIC, WIDX_MUSIC_DROPDOWN, WIDX_SAVE_TRACK_DESIGN = 14, WIDX_SELECT_NEARBY_SCENERY, WIDX_RESET_SELECTION, WIDX_SAVE_DESIGN, WIDX_CANCEL_DESIGN, WIDX_GRAPH = 14, WIDX_GRAPH_VELOCITY, WIDX_GRAPH_ALTITUDE, WIDX_GRAPH_VERTICAL, WIDX_GRAPH_LATERAL, WIDX_PRIMARY_PRICE_LABEL = 14, WIDX_PRIMARY_PRICE, WIDX_PRIMARY_PRICE_INCREASE, WIDX_PRIMARY_PRICE_DECREASE, WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK, WIDX_SECONDARY_PRICE_LABEL, WIDX_SECONDARY_PRICE, WIDX_SECONDARY_PRICE_INCREASE, WIDX_SECONDARY_PRICE_DECREASE, WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK, WIDX_SHOW_GUESTS_THOUGHTS = 14, WIDX_SHOW_GUESTS_ON_RIDE, WIDX_SHOW_GUESTS_QUEUING }; constexpr int32_t RCT1_LIGHT_OFFSET = 4; #define MAIN_RIDE_WIDGETS \ WINDOW_SHIM(WINDOW_TITLE, WW, WH), \ MakeWidget({ 0, 43}, {316, 137}, WindowWidgetType::Resize, WindowColour::Secondary), \ MakeTab ({ 3, 17}, STR_VIEW_OF_RIDE_ATTRACTION_TIP ), \ MakeTab ({ 34, 17}, STR_VEHICLE_DETAILS_AND_OPTIONS_TIP ), \ MakeTab ({ 65, 17}, STR_OPERATING_OPTIONS_TIP ), \ MakeTab ({ 96, 17}, STR_MAINTENANCE_OPTIONS_TIP ), \ MakeTab ({127, 17}, STR_COLOUR_SCHEME_OPTIONS_TIP ), \ MakeTab ({158, 17}, STR_SOUND_AND_MUSIC_OPTIONS_TIP ), \ MakeTab ({189, 17}, STR_MEASUREMENTS_AND_TEST_DATA_TIP ), \ MakeTab ({220, 17}, STR_GRAPHS_TIP ), \ MakeTab ({251, 17}, STR_INCOME_AND_COSTS_TIP ), \ MakeTab ({282, 17}, STR_CUSTOMER_INFORMATION_TIP ) // 0x009ADC34 static rct_widget window_ride_main_widgets[] = { MAIN_RIDE_WIDGETS, MakeWidget({ 3, 60}, {288, 107}, WindowWidgetType::Viewport, WindowColour::Secondary, STR_VIEWPORT ), MakeWidget({ 35, 46}, {222, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, 0xFFFFFFFF, STR_VIEW_SELECTION ), MakeWidget({245, 47}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_VIEW_SELECTION ), MakeWidget({ 3, 167}, {288, 11}, WindowWidgetType::LabelCentred, WindowColour::Secondary ), MakeWidget({291, 46}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_OPEN_CLOSE_OR_TEST_RIDE), MakeWidget({291, 70}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_CONSTRUCTION, STR_CONSTRUCTION ), MakeWidget({291, 94}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_RENAME, STR_NAME_RIDE_TIP ), MakeWidget({291, 118}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_LOCATE, STR_LOCATE_SUBJECT_TIP ), MakeWidget({291, 142}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_DEMOLISH, STR_DEMOLISH_RIDE_TIP ), MakeWidget({296, 48}, { 14, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, SPR_G2_RCT1_CLOSE_BUTTON_0, STR_CLOSE_RIDE_TIP ), MakeWidget({296, 62}, { 14, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, SPR_G2_RCT1_TEST_BUTTON_0, STR_SIMULATE_RIDE_TIP ), MakeWidget({296, 62}, { 14, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, SPR_G2_RCT1_TEST_BUTTON_0, STR_TEST_RIDE_TIP ), MakeWidget({296, 76}, { 14, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, SPR_G2_RCT1_OPEN_BUTTON_0, STR_OPEN_RIDE_TIP ), MakeWidget({ 3, 180}, {305, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_ARG_6_STRINGID ), MakeWidget({297, 180}, { 11, 12}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH ), WIDGETS_END, }; // 0x009ADDA8 static rct_widget window_ride_vehicle_widgets[] = { MAIN_RIDE_WIDGETS, MakeWidget ({ 7, 50}, {302, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary ), MakeWidget ({297, 51}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH ), MakeWidget ({ 7, 147}, {302, 43}, WindowWidgetType::Scroll, WindowColour::Secondary, STR_EMPTY ), MakeSpinnerWidgets({ 7, 196}, {145, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_RIDE_VEHICLE_COUNT, STR_MAX_VEHICLES_TIP ), MakeSpinnerWidgets({164, 196}, {145, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_1_CAR_PER_TRAIN, STR_MAX_CARS_PER_TRAIN_TIP), WIDGETS_END, }; // 0x009ADEFC static rct_widget window_ride_operating_widgets[] = { MAIN_RIDE_WIDGETS, MakeSpinnerWidgets({157, 61}, {152, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_ARG_18_STRINGID ), // NB: 3 widgets MakeSpinnerWidgets({157, 75}, {152, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_LIFT_HILL_CHAIN_SPEED_VALUE ), // NB: 3 widgets MakeWidget ({ 7, 109}, { 80, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_WAIT_FOR, STR_WAIT_FOR_PASSENGERS_BEFORE_DEPARTING_TIP), MakeWidget ({ 7, 124}, {302, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary ), MakeWidget ({ 7, 139}, {150, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_MINIMUM_WAITING_TIME, STR_MINIMUM_LENGTH_BEFORE_DEPARTING_TIP ), MakeSpinnerWidgets({157, 139}, {152, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_ARG_10_STRINGID ), // NB: 3 widgets MakeWidget ({ 7, 154}, {150, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_MAXIMUM_WAITING_TIME, STR_MAXIMUM_LENGTH_BEFORE_DEPARTING_TIP ), MakeSpinnerWidgets({157, 154}, {152, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_ARG_14_STRINGID ), // NB: 3 widgets MakeWidget ({ 7, 169}, {302, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_SYNCHRONISE_WITH_ADJACENT_STATIONS, STR_SYNCHRONISE_WITH_ADJACENT_STATIONS_TIP ), MakeWidget ({ 21, 61}, {129, 12}, WindowWidgetType::Label, WindowColour::Secondary ), MakeWidget ({ 21, 75}, {129, 12}, WindowWidgetType::Label, WindowColour::Secondary, STR_LIFT_HILL_CHAIN_SPEED ), MakeWidget ({ 7, 47}, {302, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_OPERATING_MODE ), MakeWidget ({297, 48}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_OPERATING_MODE ), MakeWidget ({ 87, 109}, {222, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary ), MakeWidget ({297, 110}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH ), MakeWidget ({ 21, 89}, {129, 12}, WindowWidgetType::Label, WindowColour::Secondary, STR_NUMBER_OF_CIRCUITS, STR_NUMBER_OF_CIRCUITS_TIP ), MakeSpinnerWidgets({157, 89}, {152, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_NUMBER_OF_CIRCUITS_VALUE ), // NB: 3 widgets WIDGETS_END, }; // 0x009AE190 static rct_widget window_ride_maintenance_widgets[] = { MAIN_RIDE_WIDGETS, MakeWidget({107, 71}, {202, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_EMPTY, STR_SELECT_HOW_OFTEN_A_MECHANIC_SHOULD_CHECK_THIS_RIDE), MakeWidget({297, 72}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_HOW_OFTEN_A_MECHANIC_SHOULD_CHECK_THIS_RIDE), MakeWidget({289, 108}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_LOCATE_NEAREST_AVAILABLE_MECHANIC_TIP ), MakeWidget({265, 108}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_CONSTRUCTION, STR_REFURBISH_RIDE_TIP ), MakeWidget({241, 108}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_NO_ENTRY, STR_DEBUG_FORCE_BREAKDOWN_TIP ), WIDGETS_END, }; // 0x009AE2A4 static rct_widget window_ride_colour_widgets[] = { MAIN_RIDE_WIDGETS, MakeWidget({ 3, 47}, { 68, 47}, WindowWidgetType::Spinner, WindowColour::Secondary ), MakeWidget({ 74, 49}, {239, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_ARG_14_STRINGID ), MakeWidget({301, 50}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_COLOUR_SCHEME_TO_CHANGE_TIP ), MakeWidget({ 79, 74}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_MAIN_COLOUR_TIP ), MakeWidget({ 99, 74}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_ADDITIONAL_COLOUR_1_TIP ), MakeWidget({119, 74}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_SUPPORT_STRUCTURE_COLOUR_TIP ), MakeWidget({ 74, 49}, {239, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary ), MakeWidget({301, 50}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH ), MakeWidget({289, 68}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_PAINTBRUSH, STR_PAINT_INDIVIDUAL_AREA_TIP ), MakeWidget({245, 101}, { 68, 47}, WindowWidgetType::Spinner, WindowColour::Secondary ), MakeWidget({103, 103}, {139, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_EMPTY ), MakeWidget({230, 104}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_STYLE_OF_ENTRANCE_EXIT_STATION_TIP), MakeWidget({ 3, 157}, { 68, 47}, WindowWidgetType::Scroll, WindowColour::Secondary, STR_EMPTY ), MakeWidget({ 74, 157}, {239, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_ARG_6_STRINGID ), MakeWidget({301, 158}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_VEHICLE_COLOUR_SCHEME_TIP ), MakeWidget({ 74, 173}, {239, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary ), MakeWidget({301, 174}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_VEHICLE_TO_MODIFY_TIP ), MakeWidget({ 79, 190}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_MAIN_COLOUR_TIP ), MakeWidget({ 99, 190}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_ADDITIONAL_COLOUR_1_TIP ), MakeWidget({119, 190}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_ADDITIONAL_COLOUR_2_TIP ), MakeWidget({100, 74}, {239, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_RANDOM_COLOUR ), WIDGETS_END, }; // 0x009AE4C8 static rct_widget window_ride_music_widgets[] = { MAIN_RIDE_WIDGETS, MakeWidget({ 7, 47}, {302, 12}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_PLAY_MUSIC, STR_SELECT_MUSIC_TIP ), MakeWidget({ 7, 62}, {302, 12}, WindowWidgetType::DropdownMenu, WindowColour::Secondary, STR_EMPTY ), MakeWidget({297, 63}, { 11, 10}, WindowWidgetType::Button, WindowColour::Secondary, STR_DROPDOWN_GLYPH, STR_SELECT_MUSIC_STYLE_TIP), WIDGETS_END, }; // 0x009AE5DC static rct_widget window_ride_measurements_widgets[] = { MAIN_RIDE_WIDGETS, MakeWidget({288, 194}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_FLOPPY, STR_SAVE_TRACK_DESIGN), MakeWidget({ 4, 127}, {154, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_SELECT_NEARBY_SCENERY ), MakeWidget({158, 127}, {154, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RESET_SELECTION ), MakeWidget({ 4, 177}, {154, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_DESIGN_SAVE ), MakeWidget({158, 177}, {154, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_DESIGN_CANCEL ), WIDGETS_END, }; // 0x009AE710 static rct_widget window_ride_graphs_widgets[] = { MAIN_RIDE_WIDGETS, MakeWidget({ 3, 46}, {306, 112}, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_HORIZONTAL, STR_LOGGING_DATA_FROM_TIP ), MakeWidget({ 3, 163}, { 73, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_VELOCITY, STR_SHOW_GRAPH_OF_VELOCITY_AGAINST_TIME_TIP ), MakeWidget({ 76, 163}, { 73, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_ALTITUDE, STR_SHOW_GRAPH_OF_ALTITUDE_AGAINST_TIME_TIP ), MakeWidget({149, 163}, { 73, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_VERT_G, STR_SHOW_GRAPH_OF_VERTICAL_ACCELERATION_AGAINST_TIME_TIP), MakeWidget({222, 163}, { 73, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_STATS_LAT_G, STR_SHOW_GRAPH_OF_LATERAL_ACCELERATION_AGAINST_TIME_TIP ), WIDGETS_END, }; // 0x009AE844 static rct_widget window_ride_income_widgets[] = { MAIN_RIDE_WIDGETS, MakeWidget ({ 19, 50}, {126, 14}, WindowWidgetType::Label, WindowColour::Secondary ), MakeSpinnerWidgets({147, 50}, {162, 14}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_ARG_6_CURRENCY2DP ), // NB: 3 widgets MakeWidget ({ 5, 62}, {306, 13}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_SAME_PRICE_THROUGHOUT_PARK, STR_SAME_PRICE_THROUGHOUT_PARK_TIP), MakeWidget ({ 19, 94}, {126, 14}, WindowWidgetType::Label, WindowColour::Secondary ), MakeSpinnerWidgets({147, 94}, {162, 14}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_RIDE_SECONDARY_PRICE_VALUE ), // NB: 3 widgets MakeWidget ({ 5, 106}, {306, 13}, WindowWidgetType::Checkbox, WindowColour::Secondary, STR_SAME_PRICE_THROUGHOUT_PARK, STR_SAME_PRICE_THROUGHOUT_PARK_TIP), WIDGETS_END, }; // 0x009AE9C8 static rct_widget window_ride_customer_widgets[] = { MAIN_RIDE_WIDGETS, MakeWidget({289, 54}, {24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_SHOW_GUESTS_THOUGHTS_ABOUT_THIS_RIDE_ATTRACTION, STR_SHOW_GUESTS_THOUGHTS_ABOUT_THIS_RIDE_ATTRACTION_TIP), MakeWidget({289, 78}, {24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_SHOW_GUESTS_ON_THIS_RIDE_ATTRACTION, STR_SHOW_GUESTS_ON_THIS_RIDE_ATTRACTION_TIP ), MakeWidget({289, 102}, {24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, SPR_SHOW_GUESTS_QUEUING_FOR_THIS_RIDE_ATTRACTION, STR_SHOW_GUESTS_QUEUING_FOR_THIS_RIDE_ATTRACTION_TIP ), WIDGETS_END, }; static rct_widget *window_ride_page_widgets[] = { window_ride_main_widgets, window_ride_vehicle_widgets, window_ride_operating_widgets, window_ride_maintenance_widgets, window_ride_colour_widgets, window_ride_music_widgets, window_ride_measurements_widgets, window_ride_graphs_widgets, window_ride_income_widgets, window_ride_customer_widgets, }; static constexpr const uint64_t window_ride_page_hold_down_widgets[] = { 0, (1ULL << WIDX_VEHICLE_TRAINS_INCREASE) | (1ULL << WIDX_VEHICLE_TRAINS_DECREASE) | (1ULL << WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE) | (1ULL << WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE), (1ULL << WIDX_MODE_TWEAK_INCREASE) | (1ULL << WIDX_MODE_TWEAK_DECREASE) | (1ULL << WIDX_LIFT_HILL_SPEED_INCREASE) | (1ULL << WIDX_LIFT_HILL_SPEED_DECREASE) | (1ULL << WIDX_MINIMUM_LENGTH_INCREASE) | (1ULL << WIDX_MINIMUM_LENGTH_DECREASE) | (1ULL << WIDX_MAXIMUM_LENGTH_INCREASE) | (1ULL << WIDX_MAXIMUM_LENGTH_DECREASE) | (1ULL << WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE) | (1ULL << WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE), 0, 0, 0, 0, 0, (1ULL << WIDX_PRIMARY_PRICE_INCREASE) | (1ULL << WIDX_PRIMARY_PRICE_DECREASE) | (1ULL << WIDX_SECONDARY_PRICE_INCREASE) | (1ULL << WIDX_SECONDARY_PRICE_DECREASE), 0, }; // clang-format on #pragma endregion #pragma region Events static void WindowRideInitViewport(rct_window* w); static void WindowRideMainMouseup(rct_window* w, WidgetIndex widgetIndex); static void WindowRideMainResize(rct_window* w); static void WindowRideMainMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget); static void WindowRideMainDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex); static void WindowRideMainUpdate(rct_window* w); static void WindowRideMainTextinput(rct_window* w, WidgetIndex widgetIndex, char* text); static void WindowRideMainViewportRotate(rct_window* w); static void WindowRideMainInvalidate(rct_window* w); static void WindowRideMainPaint(rct_window* w, rct_drawpixelinfo* dpi); static void WindowRideMainFollowRide(rct_window* w); static void WindowRideVehicleMouseup(rct_window* w, WidgetIndex widgetIndex); static void WindowRideVehicleResize(rct_window* w); static void WindowRideVehicleMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget); static void WindowRideVehicleDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex); static void WindowRideVehicleUpdate(rct_window* w); static OpenRCT2String WindowRideVehicleTooltip(rct_window* const w, const WidgetIndex widgetIndex, StringId fallback); static void WindowRideVehicleInvalidate(rct_window* w); static void WindowRideVehiclePaint(rct_window* w, rct_drawpixelinfo* dpi); static void WindowRideVehicleScrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex); static void WindowRideOperatingMouseup(rct_window* w, WidgetIndex widgetIndex); static void WindowRideOperatingResize(rct_window* w); static void WindowRideOperatingMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget); static void WindowRideOperatingLengthWindow(rct_window* w, WidgetIndex widgetIndex); static void WindowRideOperatingTweakTextInput(rct_window* w, const Ride& ride); static void WindowRideOperatingDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex); static void WindowRideOperatingUpdate(rct_window* w); static void WindowRideOperatingTextinput(rct_window* w, WidgetIndex widgetIndex, char* text); static void WindowRideOperatingInvalidate(rct_window* w); static void WindowRideOperatingPaint(rct_window* w, rct_drawpixelinfo* dpi); static void WindowRideMaintenanceMouseup(rct_window* w, WidgetIndex widgetIndex); static void WindowRideMaintenanceResize(rct_window* w); static void WindowRideMaintenanceMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget); static void WindowRideMaintenanceDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex); static void WindowRideMaintenanceUpdate(rct_window* w); static void WindowRideMaintenanceInvalidate(rct_window* w); static void WindowRideMaintenancePaint(rct_window* w, rct_drawpixelinfo* dpi); static void WindowRideColourClose(rct_window* w); static void WindowRideColourMouseup(rct_window* w, WidgetIndex widgetIndex); static void WindowRideColourResize(rct_window* w); static void WindowRideColourMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget); static void WindowRideColourDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex); static void WindowRideColourUpdate(rct_window* w); static void WindowRideColourTooldown(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); static void WindowRideColourTooldrag(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); static void WindowRideColourInvalidate(rct_window* w); static void WindowRideColourPaint(rct_window* w, rct_drawpixelinfo* dpi); static void WindowRideColourScrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex); static void WindowRideMusicMouseup(rct_window* w, WidgetIndex widgetIndex); static void WindowRideMusicResize(rct_window* w); static void WindowRideMusicMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget); static void WindowRideMusicDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex); static void WindowRideMusicUpdate(rct_window* w); static void WindowRideMusicInvalidate(rct_window* w); static void WindowRideMusicPaint(rct_window* w, rct_drawpixelinfo* dpi); static void WindowRideMeasurementsClose(rct_window* w); static void WindowRideMeasurementsMouseup(rct_window* w, WidgetIndex widgetIndex); static void WindowRideMeasurementsResize(rct_window* w); static void WindowRideMeasurementsMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget); static void WindowRideMeasurementsDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex); static void WindowRideMeasurementsUpdate(rct_window* w); static void WindowRideMeasurementsTooldown(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); static void WindowRideMeasurementsTooldrag(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); static void WindowRideMeasurementsToolabort(rct_window* w, WidgetIndex widgetIndex); static void WindowRideMeasurementsInvalidate(rct_window* w); static void WindowRideMeasurementsPaint(rct_window* w, rct_drawpixelinfo* dpi); static void WindowRideGraphsMouseup(rct_window* w, WidgetIndex widgetIndex); static void WindowRideGraphsResize(rct_window* w); static void WindowRideGraphsMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget); static void WindowRideGraphsUpdate(rct_window* w); static void WindowRideGraphsScrollgetheight(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height); static void WindowRideGraphs15(rct_window* w, int32_t scrollIndex, int32_t scrollAreaType); static OpenRCT2String WindowRideGraphsTooltip(rct_window* w, const WidgetIndex widgetIndex, const StringId fallback); static void WindowRideGraphsInvalidate(rct_window* w); static void WindowRideGraphsPaint(rct_window* w, rct_drawpixelinfo* dpi); static void WindowRideGraphsScrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex); static void WindowRideIncomeMouseup(rct_window* w, WidgetIndex widgetIndex); static void WindowRideIncomeResize(rct_window* w); static void WindowRideIncomeMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget); static void WindowRideIncomeUpdate(rct_window* w); static void WindowRideIncomeTextinput(rct_window* w, WidgetIndex widgetIndex, char* text); static void WindowRideIncomeInvalidate(rct_window* w); static void WindowRideIncomePaint(rct_window* w, rct_drawpixelinfo* dpi); static bool WindowRideIncomeCanModifyPrimaryPrice(rct_window* w); static void WindowRideCustomerMouseup(rct_window* w, WidgetIndex widgetIndex); static void WindowRideCustomerResize(rct_window* w); static void WindowRideCustomerUpdate(rct_window* w); static void WindowRideCustomerInvalidate(rct_window* w); static void WindowRideCustomerPaint(rct_window* w, rct_drawpixelinfo* dpi); static void WindowRideSetPage(rct_window* w, int32_t page); // 0x0098DFD4 static WindowEventList window_ride_main_events([](auto& events) { events.mouse_up = &WindowRideMainMouseup; events.resize = &WindowRideMainResize; events.mouse_down = &WindowRideMainMousedown; events.dropdown = &WindowRideMainDropdown; events.update = &WindowRideMainUpdate; events.text_input = &WindowRideMainTextinput; events.viewport_rotate = &WindowRideMainViewportRotate; events.invalidate = &WindowRideMainInvalidate; events.paint = &WindowRideMainPaint; }); // 0x0098E204 static WindowEventList window_ride_vehicle_events([](auto& events) { events.mouse_up = &WindowRideVehicleMouseup; events.resize = &WindowRideVehicleResize; events.mouse_down = &WindowRideVehicleMousedown; events.dropdown = &WindowRideVehicleDropdown; events.update = &WindowRideVehicleUpdate; events.tooltip = &WindowRideVehicleTooltip; events.invalidate = &WindowRideVehicleInvalidate; events.paint = &WindowRideVehiclePaint; events.scroll_paint = &WindowRideVehicleScrollpaint; }); // 0x0098E0B4 static WindowEventList window_ride_operating_events([](auto& events) { events.mouse_up = &WindowRideOperatingMouseup; events.resize = &WindowRideOperatingResize; events.mouse_down = &WindowRideOperatingMousedown; events.dropdown = &WindowRideOperatingDropdown; events.update = &WindowRideOperatingUpdate; events.text_input = &WindowRideOperatingTextinput; events.invalidate = &WindowRideOperatingInvalidate; events.paint = &WindowRideOperatingPaint; }); // 0x0098E124 static WindowEventList window_ride_maintenance_events([](auto& events) { events.mouse_up = &WindowRideMaintenanceMouseup; events.resize = &WindowRideMaintenanceResize; events.mouse_down = &WindowRideMaintenanceMousedown; events.dropdown = &WindowRideMaintenanceDropdown; events.update = &WindowRideMaintenanceUpdate; events.invalidate = &WindowRideMaintenanceInvalidate; events.paint = &WindowRideMaintenancePaint; }); // 0x0098E044 static WindowEventList window_ride_colour_events([](auto& events) { events.close = &WindowRideColourClose; events.mouse_up = &WindowRideColourMouseup; events.resize = &WindowRideColourResize; events.mouse_down = &WindowRideColourMousedown; events.dropdown = &WindowRideColourDropdown; events.update = &WindowRideColourUpdate; events.tool_down = &WindowRideColourTooldown; events.tool_drag = &WindowRideColourTooldrag; events.invalidate = &WindowRideColourInvalidate; events.paint = &WindowRideColourPaint; events.scroll_paint = &WindowRideColourScrollpaint; }); // 0x0098E194 static WindowEventList window_ride_music_events([](auto& events) { events.mouse_up = &WindowRideMusicMouseup; events.resize = &WindowRideMusicResize; events.mouse_down = &WindowRideMusicMousedown; events.dropdown = &WindowRideMusicDropdown; events.update = &WindowRideMusicUpdate; events.invalidate = &WindowRideMusicInvalidate; events.paint = &WindowRideMusicPaint; }); // 0x0098DE14 static WindowEventList window_ride_measurements_events([](auto& events) { events.close = &WindowRideMeasurementsClose; events.mouse_up = &WindowRideMeasurementsMouseup; events.resize = &WindowRideMeasurementsResize; events.mouse_down = &WindowRideMeasurementsMousedown; events.dropdown = &WindowRideMeasurementsDropdown; events.update = &WindowRideMeasurementsUpdate; events.tool_down = &WindowRideMeasurementsTooldown; events.tool_drag = &WindowRideMeasurementsTooldrag; events.tool_abort = &WindowRideMeasurementsToolabort; events.invalidate = &WindowRideMeasurementsInvalidate; events.paint = &WindowRideMeasurementsPaint; }); // 0x0098DF64 static WindowEventList window_ride_graphs_events([](auto& events) { events.mouse_up = &WindowRideGraphsMouseup; events.resize = &WindowRideGraphsResize; events.mouse_down = &WindowRideGraphsMousedown; events.update = &WindowRideGraphsUpdate; events.get_scroll_size = &WindowRideGraphsScrollgetheight; events.scroll_select = &WindowRideGraphs15; events.tooltip = &WindowRideGraphsTooltip; events.invalidate = &WindowRideGraphsInvalidate; events.paint = &WindowRideGraphsPaint; events.scroll_paint = &WindowRideGraphsScrollpaint; }); // 0x0098DEF4 static WindowEventList window_ride_income_events([](auto& events) { events.mouse_up = &WindowRideIncomeMouseup; events.resize = &WindowRideIncomeResize; events.mouse_down = &WindowRideIncomeMousedown; events.update = &WindowRideIncomeUpdate; events.text_input = &WindowRideIncomeTextinput; events.invalidate = &WindowRideIncomeInvalidate; events.paint = &WindowRideIncomePaint; }); // 0x0098DE84 static WindowEventList window_ride_customer_events([](auto& events) { events.mouse_up = &WindowRideCustomerMouseup; events.resize = &WindowRideCustomerResize; events.update = &WindowRideCustomerUpdate; events.invalidate = &WindowRideCustomerInvalidate; events.paint = &WindowRideCustomerPaint; }); // clang-format off static WindowEventList *window_ride_page_events[] = { &window_ride_main_events, &window_ride_vehicle_events, &window_ride_operating_events, &window_ride_maintenance_events, &window_ride_colour_events, &window_ride_music_events, &window_ride_measurements_events, &window_ride_graphs_events, &window_ride_income_events, &window_ride_customer_events, }; // clang-format on #pragma endregion static bool _collectTrackDesignScenery = false; static int32_t _lastSceneryX = 0; static int32_t _lastSceneryY = 0; static std::unique_ptr _trackDesign; // Cached overall view for each ride // (Re)calculated when the ride window is opened struct RideOverallView { CoordsXYZ loc; ZoomLevel zoom; }; static std::vector ride_overall_views = {}; static constexpr const int32_t window_ride_tab_animation_divisor[] = { 0, // WINDOW_RIDE_PAGE_MAIN 0, // WINDOW_RIDE_PAGE_VEHICLE 2, // WINDOW_RIDE_PAGE_OPERATING 2, // WINDOW_RIDE_PAGE_MAINTENANCE 4, // WINDOW_RIDE_PAGE_COLOUR 2, // WINDOW_RIDE_PAGE_MUSIC 8, // WINDOW_RIDE_PAGE_MEASUREMENTS 8, // WINDOW_RIDE_PAGE_GRAPHS 2, // WINDOW_RIDE_PAGE_INCOME 0, // WINDOW_RIDE_PAGE_CUSTOMER }; static_assert(std::size(window_ride_tab_animation_divisor) == WINDOW_RIDE_PAGE_COUNT); static constexpr const int32_t window_ride_tab_animation_frames[] = { 0, // WINDOW_RIDE_PAGE_MAIN 0, // WINDOW_RIDE_PAGE_VEHICLE 4, // WINDOW_RIDE_PAGE_OPERATING 16, // WINDOW_RIDE_PAGE_MAINTENANCE 8, // WINDOW_RIDE_PAGE_COLOUR 16, // WINDOW_RIDE_PAGE_MUSIC 8, // WINDOW_RIDE_PAGE_MEASUREMENTS 8, // WINDOW_RIDE_PAGE_GRAPHS 8, // WINDOW_RIDE_PAGE_INCOME 0, // WINDOW_RIDE_PAGE_CUSTOMER }; static_assert(std::size(window_ride_tab_animation_frames) == WINDOW_RIDE_PAGE_COUNT); // clang-format off static constexpr const StringId RatingNames[] = { STR_RATING_LOW, STR_RATING_MEDIUM, STR_RATING_HIGH, STR_RATING_VERY_HIGH, STR_RATING_EXTREME, STR_RATING_ULTRA_EXTREME, }; // clang-format on static constexpr const StringId RideBreakdownReasonNames[] = { STR_RIDE_BREAKDOWN_SAFETY_CUT_OUT, // BREAKDOWN_SAFETY_CUT_OUT STR_RIDE_BREAKDOWN_RESTRAINTS_STUCK_CLOSED, // BREAKDOWN_RESTRAINTS_STUCK_CLOSED STR_RIDE_BREAKDOWN_RESTRAINTS_STUCK_OPEN, // BREAKDOWN_RESTRAINTS_STUCK_OPEN STR_RIDE_BREAKDOWN_DOORS_STUCK_CLOSED, // BREAKDOWN_DOORS_STUCK_CLOSED STR_RIDE_BREAKDOWN_DOORS_STUCK_OPEN, // BREAKDOWN_DOORS_STUCK_OPEN STR_RIDE_BREAKDOWN_VEHICLE_MALFUNCTION, // BREAKDOWN_VEHICLE_MALFUNCTION STR_RIDE_BREAKDOWN_BRAKES_FAILURE, // BREAKDOWN_BRAKES_FAILURE STR_RIDE_BREAKDOWN_CONTROL_FAILURE, // BREAKDOWN_CONTROL_FAILURE }; static_assert(std::size(RideBreakdownReasonNames) == BREAKDOWN_COUNT); const StringId ColourSchemeNames[] = { STR_MAIN_COLOUR_SCHEME, // RIDE_COLOUR_SCHEME_MAIN STR_ALTERNATIVE_COLOUR_SCHEME_1, // RIDE_COLOUR_SCHEME_ADDITIONAL_1 STR_ALTERNATIVE_COLOUR_SCHEME_2, // RIDE_COLOUR_SCHEME_ADDITIONAL_2 STR_ALTERNATIVE_COLOUR_SCHEME_3, // RIDE_COLOUR_SCHEME_ADDITIONAL_3 }; static_assert(std::size(ColourSchemeNames) == RIDE_COLOUR_SCHEME_COUNT); static constexpr const StringId VehicleLoadNames[] = { STR_QUARTER_LOAD, // WAIT_FOR_LOAD_QUARTER STR_HALF_LOAD, // WAIT_FOR_LOAD_HALF STR_THREE_QUARTER_LOAD, // WAIT_FOR_LOAD_THREE_QUARTER STR_FULL_LOAD, // WAIT_FOR_LOAD_FULL STR_ANY_LOAD, // WAIT_FOR_LOAD_ANY }; static_assert(std::size(VehicleLoadNames) == WAIT_FOR_LOAD_COUNT); static constexpr const StringId VehicleColourSchemeNames[] = { STR_ALL_VEHICLES_IN_SAME_COLOURS, // RIDE_COLOUR_SCHEME_MODE_ALL_SAME, STR_DIFFERENT_COLOURS_PER, // RIDE_COLOUR_SCHEME_MODE_DIFFERENT_PER_TRAIN, STR_DIFFERENT_COLOURS_PER_VEHICLE, // RIDE_COLOUR_SCHEME_MODE_DIFFERENT_PER_CAR, }; static_assert(std::size(VehicleColourSchemeNames) == RIDE_COLOUR_SCHEME_MODE_COUNT); static constexpr const StringId VehicleStatusNames[] = { STR_MOVING_TO_END_OF, // Vehicle::Status::MovingToEndOfStation STR_WAITING_FOR_PASSENGERS_AT, // Vehicle::Status::WaitingForPassengers STR_WAITING_TO_DEPART, // Vehicle::Status::WaitingToDepart STR_DEPARTING, // Vehicle::Status::Departing STR_TRAVELLING_AT_0, // Vehicle::Status::Travelling STR_ARRIVING_AT, // Vehicle::Status::Arriving STR_UNLOADING_PASSENGERS_AT, // Vehicle::Status::UnloadingPassengers STR_TRAVELLING_AT_1, // Vehicle::Status::TravellingBoat STR_CRASHING, // Vehicle::Status::Crashing STR_CRASHED_0, // Vehicle::Status::Crashed STR_TRAVELLING_AT_2, // Vehicle::Status::TravellingDodgems STR_SWINGING, // Vehicle::Status::Swinging STR_ROTATING_0, // Vehicle::Status::Rotating STR_ROTATING_1, // Vehicle::Status::FerrisWheelRotating STR_OPERATING_0, // Vehicle::Status::SimulatorOperating STR_SHOWING_FILM, // Vehicle::Status::ShowingFilm STR_ROTATING_2, // Vehicle::Status::SpaceRingsOperating STR_OPERATING_1, // Vehicle::Status::TopSpinOperating STR_OPERATING_2, // Vehicle::Status::HauntedHouseOperating STR_DOING_CIRCUS_SHOW, // Vehicle::Status::DoingCircusShow STR_OPERATING_3, // Vehicle::Status::CrookedHouseOperating STR_WAITING_FOR_CABLE_LIFT, // Vehicle::Status::WaitingForCableLift STR_TRAVELLING_AT_3, // Vehicle::Status::TravellingCableLift STR_STOPPING_0, // Vehicle::Status::Stopping STR_WAITING_FOR_PASSENGERS, // Vehicle::Status::WaitingForPassengers17 STR_WAITING_TO_START, // Vehicle::Status::WaitingToStart STR_STARTING, // Vehicle::Status::Starting STR_OPERATING, // Vehicle::Status::Operating1A STR_STOPPING_1, // Vehicle::Status::Stopping1B STR_UNLOADING_PASSENGERS, // Vehicle::Status::UnloadingPassengers1C STR_STOPPED_BY_BLOCK_BRAKES, // Vehicle::Status::StoppedByBlockBrakes }; static constexpr const StringId SingleSessionVehicleStatusNames[] = { STR_STOPPING_0, // Vehicle::Status::MovingToEndOfStation STR_WAITING_FOR_PASSENGERS, // Vehicle::Status::WaitingForPassengers STR_WAITING_TO_START, // Vehicle::Status::WaitingToDepart STR_STARTING, // Vehicle::Status::Departing STR_OPERATING, // Vehicle::Status::Travelling STR_STOPPING_1, // Vehicle::Status::Arriving STR_UNLOADING_PASSENGERS, // Vehicle::Status::UnloadingPassengers }; struct WindowRideMazeDesignOption { StringId text; uint32_t sprite; }; static constexpr const WindowRideMazeDesignOption MazeOptions[] = { { STR_RIDE_DESIGN_MAZE_BRICK_WALLS, SPR_RIDE_DESIGN_PREVIEW_MAZE_BRICK_WALLS }, { STR_RIDE_DESIGN_MAZE_HEDGES, SPR_RIDE_DESIGN_PREVIEW_MAZE_HEDGES }, { STR_RIDE_DESIGN_MAZE_ICE_BLOCKS, SPR_RIDE_DESIGN_PREVIEW_MAZE_ICE_BLOCKS }, { STR_RIDE_DESIGN_MAZE_WOODEN_FENCES, SPR_RIDE_DESIGN_PREVIEW_MAZE_WOODEN_FENCES }, }; struct GraphsYAxis { uint8_t interval; int8_t unit; int8_t unit_interval; StringId label; }; /** rct2: 0x0098DD98 */ static constexpr const GraphsYAxis window_graphs_y_axi[] = { { 11, 0, 10, STR_RIDE_STATS_VELOCITY_FORMAT }, // GRAPH_VELOCITY { 10, 0, 15, STR_RIDE_STATS_ALTITUDE_FORMAT }, // GRAPH_ALTITUDE { 13, -3, 1, STR_RIDE_STATS_G_FORCE_FORMAT }, // GRAPH_VERTICAL { 13, -4, 1, STR_RIDE_STATS_G_FORCE_FORMAT }, // GRAPH_LATERAL }; static constexpr auto RIDE_G_FORCES_RED_NEG_VERTICAL = -FIXED_2DP(2, 50); static constexpr auto RIDE_G_FORCES_RED_LATERAL = FIXED_2DP(2, 80); // Used for sorting the ride type cheat dropdown. struct RideTypeLabel { ride_type_t ride_type_id; StringId label_id; const char* label_string; }; static int32_t RideDropdownDataLanguage = LANGUAGE_UNDEFINED; static std::vector RideDropdownData; // Used for sorting the vehicle type dropdown. struct VehicleTypeLabel { ObjectEntryIndex subtype_id; StringId label_id; const char* label_string; }; static int32_t VehicleDropdownDataLanguage = LANGUAGE_UNDEFINED; static rct_ride_entry* VehicleDropdownRideType = nullptr; static bool VehicleDropdownExpanded = false; static std::vector VehicleDropdownData; static void WindowRideDrawTabImage(rct_drawpixelinfo* dpi, rct_window* w, int32_t page, int32_t spriteIndex) { WidgetIndex widgetIndex = WIDX_TAB_1 + page; if (!WidgetIsDisabled(*w, widgetIndex)) { if (w->page == page) { int32_t frame = w->frame_no / window_ride_tab_animation_divisor[w->page]; spriteIndex += (frame % window_ride_tab_animation_frames[w->page]); } const auto& widget = w->widgets[widgetIndex]; gfx_draw_sprite(dpi, ImageId(spriteIndex), w->windowPos + ScreenCoordsXY{ widget.left, widget.top }); } } /** * * rct2: 0x006B2E88 */ static void WindowRideDrawTabMain(rct_drawpixelinfo* dpi, rct_window* w) { WidgetIndex widgetIndex = WIDX_TAB_1 + WINDOW_RIDE_PAGE_MAIN; if (!WidgetIsDisabled(*w, widgetIndex)) { auto ride = get_ride(w->rideId); if (ride != nullptr) { int32_t spriteIndex = 0; switch (ride->GetClassification()) { case RideClassification::Ride: spriteIndex = SPR_TAB_RIDE_0; if (w->page == WINDOW_RIDE_PAGE_MAIN) spriteIndex += (w->frame_no / 4) % 16; break; case RideClassification::ShopOrStall: spriteIndex = SPR_TAB_SHOPS_AND_STALLS_0; if (w->page == WINDOW_RIDE_PAGE_MAIN) spriteIndex += (w->frame_no / 4) % 16; break; case RideClassification::KioskOrFacility: spriteIndex = SPR_TAB_KIOSKS_AND_FACILITIES_0; if (w->page == WINDOW_RIDE_PAGE_MAIN) spriteIndex += (w->frame_no / 4) % 8; break; } const auto& widget = w->widgets[widgetIndex]; gfx_draw_sprite(dpi, ImageId(spriteIndex), w->windowPos + ScreenCoordsXY{ widget.left, widget.top }); } } } /** * * rct2: 0x006B2B68 */ static void WindowRideDrawTabVehicle(rct_drawpixelinfo* dpi, rct_window* w) { WidgetIndex widgetIndex = WIDX_TAB_1 + WINDOW_RIDE_PAGE_VEHICLE; const auto& widget = w->widgets[widgetIndex]; if (!WidgetIsDisabled(*w, widgetIndex)) { auto screenCoords = ScreenCoordsXY{ widget.left + 1, widget.top + 1 }; int32_t width = widget.right - screenCoords.x; int32_t height = widget.bottom - 3 - screenCoords.y; if (w->page == WINDOW_RIDE_PAGE_VEHICLE) height += 4; screenCoords += w->windowPos; rct_drawpixelinfo clipDPI; if (!clip_drawpixelinfo(&clipDPI, dpi, screenCoords, width, height)) { return; } screenCoords = ScreenCoordsXY{ widget.width() / 2, widget.height() - 12 }; auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr) return; if (rideEntry->flags & RIDE_ENTRY_FLAG_VEHICLE_TAB_SCALE_HALF) { clipDPI.zoom_level = ZoomLevel{ 1 }; clipDPI.width *= 2; clipDPI.height *= 2; screenCoords.x *= 2; screenCoords.y *= 2; clipDPI.x *= 2; clipDPI.y *= 2; } // For any suspended rides, move image higher in the vehicle tab on the rides window if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SUSPENDED)) { screenCoords.y /= 4; } const auto vehicle = ride_entry_get_vehicle_at_position(ride->subtype, ride->num_cars_per_train, rideEntry->TabCar); CarEntry* carEntry = &rideEntry->Cars[vehicle]; auto vehicleId = ((ride->colour_scheme_type & 3) == VEHICLE_COLOUR_SCHEME_PER_VEHICLE) ? rideEntry->TabCar : 0; VehicleColour vehicleColour = ride_get_vehicle_colour(ride, vehicleId); // imageIndex represents a precision of 64 auto imageIndex = OpenRCT2::Entity::Yaw::YawFrom4(2) * 2; if (w->page == WINDOW_RIDE_PAGE_VEHICLE) imageIndex += w->frame_no; imageIndex = carEntry->SpriteByYaw(imageIndex / 2, SpriteGroupType::SlopeFlat); imageIndex &= carEntry->TabRotationMask; imageIndex *= carEntry->base_num_frames; imageIndex += carEntry->base_image_id; auto imageId = ImageId(imageIndex, vehicleColour.Body, vehicleColour.Trim, vehicleColour.Tertiary); gfx_draw_sprite(&clipDPI, imageId, screenCoords); } } /** * * rct2: 0x006B2F42 */ static void WindowRideDrawTabCustomer(rct_drawpixelinfo* dpi, rct_window* w) { WidgetIndex widgetIndex = WIDX_TAB_1 + WINDOW_RIDE_PAGE_CUSTOMER; if (!WidgetIsDisabled(*w, widgetIndex)) { const auto& widget = w->widgets[widgetIndex]; int32_t spriteIndex = 0; if (w->page == WINDOW_RIDE_PAGE_CUSTOMER) spriteIndex = w->picked_peep_frame & ~3; spriteIndex += GetPeepAnimation(PeepSpriteType::Normal).base_image + 1; gfx_draw_sprite( dpi, ImageId(spriteIndex, COLOUR_BRIGHT_RED, COLOUR_TEAL), w->windowPos + ScreenCoordsXY{ widget.midX(), widget.bottom - 6 }); } } /** * * rct2: 0x006B2B35 */ static void WindowRideDrawTabImages(rct_drawpixelinfo* dpi, rct_window* w) { WindowRideDrawTabVehicle(dpi, w); WindowRideDrawTabImage(dpi, w, WINDOW_RIDE_PAGE_OPERATING, SPR_TAB_GEARS_0); WindowRideDrawTabImage(dpi, w, WINDOW_RIDE_PAGE_MAINTENANCE, SPR_TAB_WRENCH_0); WindowRideDrawTabImage(dpi, w, WINDOW_RIDE_PAGE_INCOME, SPR_TAB_ADMISSION_0); WindowRideDrawTabMain(dpi, w); WindowRideDrawTabImage(dpi, w, WINDOW_RIDE_PAGE_MEASUREMENTS, SPR_TAB_TIMER_0); WindowRideDrawTabImage(dpi, w, WINDOW_RIDE_PAGE_COLOUR, SPR_TAB_PAINT_0); WindowRideDrawTabImage(dpi, w, WINDOW_RIDE_PAGE_GRAPHS, SPR_TAB_GRAPH_A_0); WindowRideDrawTabCustomer(dpi, w); WindowRideDrawTabImage(dpi, w, WINDOW_RIDE_PAGE_MUSIC, SPR_TAB_MUSIC_0); } /** * * rct2: 0x006AEB9F */ static void WindowRideDisableTabs(rct_window* w) { uint32_t disabled_tabs = 0; auto ride = get_ride(w->rideId); if (ride == nullptr) return; const auto& rtd = ride->GetRideTypeDescriptor(); if (!rtd.HasFlag(RIDE_TYPE_FLAG_HAS_DATA_LOGGING)) disabled_tabs |= (1ULL << WIDX_TAB_8); // 0x800 if (ride->type == RIDE_TYPE_MINI_GOLF) disabled_tabs |= (1ULL << WIDX_TAB_2 | 1ULL << WIDX_TAB_3 | 1ULL << WIDX_TAB_4); // 0xE0 if (rtd.HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES)) disabled_tabs |= (1ULL << WIDX_TAB_2); // 0x20 if (!rtd.HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_MAIN) && !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_ADDITIONAL) && !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_SUPPORTS) && !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_VEHICLE_COLOURS) && !rtd.HasFlag(RIDE_TYPE_FLAG_HAS_ENTRANCE_EXIT)) { disabled_tabs |= (1ULL << WIDX_TAB_5); // 0x100 } if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_SHOP_OR_FACILITY)) disabled_tabs |= (1ULL << WIDX_TAB_3 | 1ULL << WIDX_TAB_4 | 1ULL << WIDX_TAB_7); // 0x4C0 if (!rtd.HasFlag(RIDE_TYPE_FLAG_ALLOW_MUSIC)) { disabled_tabs |= (1ULL << WIDX_TAB_6); // 0x200 } if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_CASH_MACHINE) || rtd.HasFlag(RIDE_TYPE_FLAG_IS_FIRST_AID) || (gParkFlags & PARK_FLAGS_NO_MONEY) != 0) disabled_tabs |= (1ULL << WIDX_TAB_9); // 0x1000 if ((gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) != 0) disabled_tabs |= (1ULL << WIDX_TAB_4 | 1ULL << WIDX_TAB_6 | 1ULL << WIDX_TAB_9 | 1ULL << WIDX_TAB_10); // 0x3280 rct_ride_entry* rideEntry = get_ride_entry(ride->subtype); if (rideEntry == nullptr) { disabled_tabs |= 1ULL << WIDX_TAB_2 | 1ULL << WIDX_TAB_3 | 1ULL << WIDX_TAB_4 | 1ULL << WIDX_TAB_5 | 1ULL << WIDX_TAB_6 | 1ULL << WIDX_TAB_7 | 1ULL << WIDX_TAB_8 | 1ULL << WIDX_TAB_9 | 1ULL << WIDX_TAB_10; } else if ((rideEntry->flags & RIDE_ENTRY_FLAG_DISABLE_COLOUR_TAB) != 0) { disabled_tabs |= (1ULL << WIDX_TAB_5); } w->disabled_widgets = disabled_tabs; } static void WindowRideUpdateOverallView(Ride* ride) { // Calculate x, y, z bounds of the entire ride using its track elements tile_element_iterator it; TileElementIteratorBegin(&it); CoordsXYZ min = { std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max() }; CoordsXYZ max = { std::numeric_limits::min(), std::numeric_limits::min(), std::numeric_limits::min() }; while (TileElementIteratorNext(&it)) { if (it.element->GetType() != TileElementType::Track) continue; if (it.element->AsTrack()->GetRideIndex() != ride->id) continue; auto location = TileCoordsXY(it.x, it.y).ToCoordsXY(); int32_t baseZ = it.element->GetBaseZ(); int32_t clearZ = it.element->GetClearanceZ(); min.x = std::min(min.x, location.x); min.y = std::min(min.y, location.y); min.z = std::min(min.z, baseZ); max.x = std::max(max.x, location.x); max.y = std::max(max.y, location.y); max.z = std::max(max.z, clearZ); } const auto rideIndex = ride->id.ToUnderlying(); if (rideIndex >= ride_overall_views.size()) { ride_overall_views.resize(rideIndex + 1); } auto& view = ride_overall_views[rideIndex]; view.loc = CoordsXYZ{ (min.x + max.x) / 2, (min.y + max.y) / 2, (min.z + max.z) / 2 } + CoordsXYZ{ 16, 16, -8 }; // Calculate size to determine from how far away to view the ride const auto diff = max - min; const int32_t size = static_cast(std::sqrt(diff.x * diff.x + diff.y * diff.y + diff.z * diff.z)); if (size >= 80) { // Each farther zoom level shows twice as many tiles (log) // Appropriate zoom is lowered by one to fill the entire view with the ride const auto zoomValue = static_cast(std::ceil(std::log(size / 80)) - 1); view.zoom = std::clamp(ZoomLevel{ zoomValue }, ZoomLevel{ 0 }, ZoomLevel::max()); } else { // Small rides or stalls are zoomed in all the way. view.zoom = ZoomLevel{ 0 }; } } /** * * rct2: 0x006AEAB4 */ static rct_window* WindowRideOpen(Ride* ride) { rct_window* w; w = WindowCreateAutoPos(316, 207, window_ride_page_events[0], WindowClass::Ride, WF_10 | WF_RESIZABLE); w->widgets = window_ride_page_widgets[WINDOW_RIDE_PAGE_MAIN]; w->hold_down_widgets = window_ride_page_hold_down_widgets[WINDOW_RIDE_PAGE_MAIN]; w->rideId = ride->id; w->page = WINDOW_RIDE_PAGE_MAIN; w->vehicleIndex = 0; w->frame_no = 0; w->list_information_type = 0; w->picked_peep_frame = 0; w->ride_colour = 0; WindowRideDisableTabs(w); w->min_width = 316; w->min_height = 180; w->max_width = 500; w->max_height = 450; WindowRideUpdateOverallView(ride); PopulateVehicleTypeDropdown(ride, true); return w; } /** * * rct2: 0x006ACC28 */ rct_window* WindowRideMainOpen(Ride* ride) { if (ride->type >= RIDE_TYPE_COUNT) { return nullptr; } rct_window* w = window_bring_to_front_by_number(WindowClass::Ride, ride->id.ToUnderlying()); if (w == nullptr) { w = WindowRideOpen(ride); w->ride.var_482 = -1; w->ride.view = 0; } else if (w->ride.view >= (1 + ride->NumTrains + ride->num_stations)) { w->ride.view = 0; } if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE)) { if (w->classification == gCurrentToolWidget.window_classification && w->number == gCurrentToolWidget.window_number) { tool_cancel(); } } if (w->page != WINDOW_RIDE_PAGE_MAIN) { WindowRideSetPage(w, WINDOW_RIDE_PAGE_MAIN); } WindowRideInitViewport(w); return w; } /** * * rct2: 0x006ACCCE */ static rct_window* WindowRideOpenStation(Ride* ride, StationIndex stationIndex) { if (ride->type >= RIDE_TYPE_COUNT) return nullptr; if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES)) return WindowRideMainOpen(ride); auto w = window_bring_to_front_by_number(WindowClass::Ride, ride->id.ToUnderlying()); if (w == nullptr) { w = WindowRideOpen(ride); w->ride.var_482 = -1; } if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE) && gCurrentToolWidget.window_classification == w->classification && gCurrentToolWidget.window_number == w->number) { tool_cancel(); } w->page = WINDOW_RIDE_PAGE_MAIN; w->width = 316; w->height = 180; w->Invalidate(); w->widgets = window_ride_page_widgets[w->page]; w->hold_down_widgets = window_ride_page_hold_down_widgets[w->page]; w->event_handlers = window_ride_page_events[w->page]; w->pressed_widgets = 0; WindowRideDisableTabs(w); WindowInitScrollWidgets(*w); // View for (int32_t i = stationIndex.ToUnderlying(); i >= 0; i--) { if (ride->GetStations()[i].Start.IsNull()) { stationIndex = StationIndex::FromUnderlying(stationIndex.ToUnderlying() - 1); } } w->ride.view = 1 + ride->NumTrains + stationIndex.ToUnderlying(); WindowRideInitViewport(w); return w; } rct_window* WindowRideOpenTrack(TileElement* tileElement) { assert(tileElement != nullptr); auto rideIndex = tileElement->GetRideIndex(); if (!rideIndex.IsNull()) { auto ride = get_ride(rideIndex); if (ride != nullptr) { const auto type = tileElement->GetType(); if (type == TileElementType::Entrance) { // Open ride window in station view auto entranceElement = tileElement->AsEntrance(); auto stationIndex = entranceElement->GetStationIndex(); return WindowRideOpenStation(ride, stationIndex); } else if (type == TileElementType::Track) { // Open ride window in station view auto trackElement = tileElement->AsTrack(); auto trackType = trackElement->GetTrackType(); const auto& ted = GetTrackElementDescriptor(trackType); if (ted.SequenceProperties[0] & TRACK_SEQUENCE_FLAG_ORIGIN) { auto stationIndex = trackElement->GetStationIndex(); return WindowRideOpenStation(ride, stationIndex); } } // Open ride window in overview mode return WindowRideMainOpen(ride); } } return nullptr; } /** * * rct2: 0x006ACAC2 */ rct_window* WindowRideOpenVehicle(Vehicle* vehicle) { if (vehicle == nullptr) return nullptr; Vehicle* headVehicle = vehicle->TrainHead(); if (headVehicle == nullptr) return nullptr; EntityId headVehicleSpriteIndex = headVehicle->sprite_index; auto ride = headVehicle->GetRide(); if (ride == nullptr) return nullptr; // Get view index int32_t view = 1; for (int32_t i = 0; i <= OpenRCT2::Limits::MaxTrainsPerRide; i++) { if (ride->vehicles[i] == headVehicleSpriteIndex) break; view++; } rct_window* w = window_find_by_number(WindowClass::Ride, ride->id.ToUnderlying()); if (w != nullptr) { w->Invalidate(); if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE) && gCurrentToolWidget.window_classification == w->classification && gCurrentToolWidget.window_number == w->number) { tool_cancel(); } int32_t openedPeepWindow = 0; if (w->ride.view == view) { int32_t numPeepsLeft = vehicle->num_peeps; for (int32_t i = 0; i < 32 && numPeepsLeft > 0; i++) { Peep* peep = GetEntity(vehicle->peep[i]); if (peep == nullptr) continue; numPeepsLeft--; rct_window* w2 = window_find_by_number(WindowClass::Peep, vehicle->peep[i]); if (w2 == nullptr) { auto intent = Intent(WindowClass::Peep); intent.putExtra(INTENT_EXTRA_PEEP, peep); context_open_intent(&intent); openedPeepWindow = 1; break; } } } w = openedPeepWindow ? window_find_by_number(WindowClass::Ride, ride->id.ToUnderlying()) : window_bring_to_front_by_number(WindowClass::Ride, ride->id.ToUnderlying()); } if (w == nullptr) { w = WindowRideOpen(ride); w->ride.var_482 = -1; } w->page = WINDOW_RIDE_PAGE_MAIN; w->width = 316; w->height = 180; w->Invalidate(); w->widgets = window_ride_page_widgets[w->page]; w->hold_down_widgets = window_ride_page_hold_down_widgets[w->page]; w->event_handlers = window_ride_page_events[w->page]; w->pressed_widgets = 0; WindowRideDisableTabs(w); WindowInitScrollWidgets(*w); w->ride.view = view; WindowRideInitViewport(w); w->Invalidate(); return w; } /** * * rct2: 0x006AF1D2 */ static void WindowRideSetPage(rct_window* w, int32_t page) { int32_t listen; if (input_test_flag(INPUT_FLAG_TOOL_ACTIVE)) if (w->classification == gCurrentToolWidget.window_classification && w->number == gCurrentToolWidget.window_number) tool_cancel(); if (page == WINDOW_RIDE_PAGE_VEHICLE) { auto constructionWindow = window_find_by_class(WindowClass::RideConstruction); if (constructionWindow != nullptr && constructionWindow->number == w->number) { window_close_by_class(WindowClass::RideConstruction); // Closing the construction window sets the tab to the first page, which we don't want here, // as user just clicked the Vehicle page WindowRideSetPage(w, WINDOW_RIDE_PAGE_VEHICLE); } } // Set listen only to viewport listen = 0; if (page == WINDOW_RIDE_PAGE_MAIN && w->page == WINDOW_RIDE_PAGE_MAIN && w->viewport != nullptr && !(w->viewport->flags & VIEWPORT_FLAG_SOUND_ON)) listen++; w->page = page; w->frame_no = 0; w->picked_peep_frame = 0; // There doesn't seem to be any need for this call, and it can sometimes modify the reported number of cars per train, so // I've removed it if (page == WINDOW_RIDE_PAGE_VEHICLE) { ride_update_max_vehicles(ride); //} w->RemoveViewport(); w->hold_down_widgets = window_ride_page_hold_down_widgets[page]; w->event_handlers = window_ride_page_events[page]; w->pressed_widgets = 0; w->widgets = window_ride_page_widgets[page]; WindowRideDisableTabs(w); w->Invalidate(); window_event_resize_call(w); window_event_invalidate_call(w); WindowInitScrollWidgets(*w); w->Invalidate(); if (listen != 0 && w->viewport != nullptr) w->viewport->flags |= VIEWPORT_FLAG_SOUND_ON; } static void WindowRideSetPressedTab(rct_window* w) { int32_t i; for (i = 0; i < WINDOW_RIDE_PAGE_COUNT; i++) w->pressed_widgets &= ~(1 << (WIDX_TAB_1 + i)); w->pressed_widgets |= 1LL << (WIDX_TAB_1 + w->page); } static void WindowRideAnchorBorderWidgets(rct_window* w) { w->widgets[WIDX_BACKGROUND].right = w->width - 1; w->widgets[WIDX_BACKGROUND].bottom = w->height - 1; w->widgets[WIDX_PAGE_BACKGROUND].right = w->width - 1; w->widgets[WIDX_PAGE_BACKGROUND].bottom = w->height - 1; w->widgets[WIDX_TITLE].right = w->width - 2; w->widgets[WIDX_CLOSE].left = w->width - 13; w->widgets[WIDX_CLOSE].right = w->width - 3; } #pragma region Main static std::optional GetStationIndexFromViewSelection(const rct_window& w) { const auto* ride = get_ride(RideId::FromUnderlying(w.number)); if (ride == nullptr) return std::nullopt; int32_t viewSelectionIndex = w.ride.view - 1 - ride->NumTrains; if (viewSelectionIndex < 0) { return std::nullopt; } for (const auto& station : ride->GetStations()) { if (!station.Start.IsNull() && viewSelectionIndex-- == 0) { const auto stationIndex = ride->GetStationIndex(&station); return std::make_optional(stationIndex); } } return std::nullopt; } /** * * rct2: 0x006AF994 */ static void WindowRideInitViewport(rct_window* w) { if (w->page != WINDOW_RIDE_PAGE_MAIN) return; auto ride = get_ride(w->rideId); if (ride == nullptr) return; int32_t viewSelectionIndex = w->ride.view - 1; std::optional focus; if (viewSelectionIndex >= 0 && viewSelectionIndex < ride->NumTrains && ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK) { auto vehId = ride->vehicles[viewSelectionIndex]; rct_ride_entry* ride_entry = ride->GetRideEntry(); if (ride_entry != nullptr && ride_entry->TabCar != 0) { Vehicle* vehicle = GetEntity(vehId); if (vehicle == nullptr) { vehId = EntityId::GetNull(); } else if (!vehicle->next_vehicle_on_train.IsNull()) { vehId = vehicle->next_vehicle_on_train; } } if (!vehId.IsNull()) { focus = Focus(vehId); } } else if (viewSelectionIndex >= ride->NumTrains && viewSelectionIndex < (ride->NumTrains + ride->num_stations)) { auto stationIndex = GetStationIndexFromViewSelection(*w); if (stationIndex) { const auto location = ride->GetStation(*stationIndex).GetStart(); focus = Focus(location); } } else { if (viewSelectionIndex > 0) { w->ride.view = 0; } if (w->number < ride_overall_views.size()) { const auto& view = ride_overall_views[w->number]; focus = Focus(view.loc, view.zoom); } } uint16_t viewport_flags = 0; if (w->viewport != nullptr) { if (focus == w->focus) { return; } viewport_flags = w->viewport->flags; w->RemoveViewport(); } else if (gConfigGeneral.always_show_gridlines) { viewport_flags |= VIEWPORT_FLAG_GRIDLINES; } window_event_invalidate_call(w); w->focus = focus; // rct2: 0x006aec9c only used here so brought it into the function if (w->viewport == nullptr && !ride->overall_view.IsNull() && w->focus.has_value()) { const auto& view_widget = w->widgets[WIDX_VIEWPORT]; auto screenPos = w->windowPos + ScreenCoordsXY{ view_widget.left + 1, view_widget.top + 1 }; int32_t width = view_widget.width() - 1; int32_t height = view_widget.height() - 1; viewport_create(w, screenPos, width, height, w->focus.value()); w->flags |= WF_NO_SCROLLING; w->Invalidate(); } if (w->viewport != nullptr) { w->viewport->flags = viewport_flags; w->Invalidate(); } } /** * * rct2: 0x006AF315 */ static void WindowRideRename(rct_window* w) { auto ride = get_ride(w->rideId); if (ride != nullptr) { auto rideName = ride->GetName(); WindowTextInputRawOpen( w, WIDX_RENAME, STR_RIDE_ATTRACTION_NAME, STR_ENTER_NEW_NAME_FOR_THIS_RIDE_ATTRACTION, {}, rideName.c_str(), 32); } } /** * * rct2: 0x006AF17E */ static void WindowRideMainMouseup(rct_window* w, WidgetIndex widgetIndex) { switch (widgetIndex) { case WIDX_CLOSE: window_close(*w); break; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: case WIDX_TAB_5: case WIDX_TAB_6: case WIDX_TAB_7: case WIDX_TAB_8: case WIDX_TAB_9: case WIDX_TAB_10: WindowRideSetPage(w, widgetIndex - WIDX_TAB_1); break; case WIDX_CONSTRUCTION: { auto ride = get_ride(w->rideId); if (ride != nullptr) { ride_construct(ride); if (window_find_by_number(WindowClass::RideConstruction, ride->id.ToUnderlying()) != nullptr) { window_close(*w); } } break; } case WIDX_RENAME: WindowRideRename(w); break; case WIDX_DEMOLISH: 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: { auto ride = get_ride(w->rideId); if (ride != nullptr) { RideStatus status; switch (widgetIndex) { default: case WIDX_CLOSE_LIGHT: status = RideStatus::Closed; break; case WIDX_SIMULATE_LIGHT: status = RideStatus::Simulating; break; case WIDX_TEST_LIGHT: status = RideStatus::Testing; break; case WIDX_OPEN_LIGHT: status = RideStatus::Open; break; } auto gameAction = RideSetStatusAction(ride->id, status); GameActions::Execute(&gameAction); } break; } } } /** * * rct2: 0x006AF4A2 */ static void WindowRideMainResize(rct_window* w) { int32_t minHeight = 180; if (ThemeGetFlags() & UITHEME_FLAG_USE_LIGHTS_RIDE) { minHeight += 20 + RCT1_LIGHT_OFFSET; auto ride = get_ride(w->rideId); if (ride != nullptr) { #ifdef __SIMULATE_IN_RIDE_WINDOW__ if (ride->SupportsStatus(RideStatus::Simulating)) { minHeight += 14; } #endif if (ride->SupportsStatus(RideStatus::Testing)) { minHeight += 14; } } } if (gCheatsAllowArbitraryRideTypeChanges) { minHeight += 15; } w->flags |= WF_RESIZABLE; window_set_resize(*w, 316, minHeight, 500, 450); // Unlike with other windows, the focus needs to be recentred so it’s best to just reset it. w->focus = std::nullopt; WindowRideInitViewport(w); } /** * * rct2: 0x006AF825 */ static void WindowRideShowViewDropdown(rct_window* w, rct_widget* widget) { rct_widget* dropdownWidget = widget - 1; auto ride = get_ride(w->rideId); if (ride == nullptr) return; int32_t numItems = 1; if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES)) { numItems += ride->num_stations; numItems += ride->NumTrains; } WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, 0, numItems, widget->right - dropdownWidget->left); // First item gDropdownItems[0].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[0].Args = STR_OVERALL_VIEW; int32_t currentItem = 1; const auto& rtd = ride->GetRideTypeDescriptor(); // Vehicles int32_t name = GetRideComponentName(rtd.NameConvention.vehicle).number; for (int32_t i = 1; i <= ride->NumTrains; i++) { gDropdownItems[currentItem].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[currentItem].Args = name | (currentItem << 16); currentItem++; } // Stations name = GetRideComponentName(rtd.NameConvention.station).number; for (int32_t i = 1; i <= ride->num_stations; i++) { gDropdownItems[currentItem].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[currentItem].Args = name | (i << 16); currentItem++; } // Set highlighted item if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)) { for (int32_t i = 0; i < ride->NumTrains; i++) { // The +1 is to skip 'Overall view' Dropdown::SetDisabled(i + 1, true); } } // Set checked item Dropdown::SetChecked(w->ride.view, true); } static RideStatus WindowRideGetNextDefaultStatus(const Ride* ride) { switch (ride->status) { default: case RideStatus::Closed: if ((ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED) || (ride->lifecycle_flags & RIDE_LIFECYCLE_HAS_STALLED_VEHICLE)) { return RideStatus::Closed; } if (ride->SupportsStatus(RideStatus::Testing) && !(ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED)) { return RideStatus::Testing; } return RideStatus::Open; case RideStatus::Simulating: return RideStatus::Testing; case RideStatus::Testing: return (ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED) ? RideStatus::Open : RideStatus::Closed; case RideStatus::Open: return RideStatus::Closed; } } struct RideStatusDropdownInfo { struct Ride* Ride{}; RideStatus CurrentStatus{}; RideStatus DefaultStatus{}; int32_t NumItems{}; int32_t CheckedIndex = -1; int32_t DefaultIndex = -1; }; static void WindowRideSetDropdown(RideStatusDropdownInfo& info, RideStatus status, StringId text) { if (info.Ride->SupportsStatus(status)) { auto index = info.NumItems; gDropdownItems[index].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[index].Args = text; if (info.CurrentStatus == status) { info.CheckedIndex = index; } if (info.DefaultStatus == status) { info.DefaultIndex = index; } info.NumItems++; } } static void WindowRideShowOpenDropdown(rct_window* w, rct_widget* widget) { RideStatusDropdownInfo info; info.Ride = get_ride(w->rideId); if (info.Ride == nullptr) return; info.CurrentStatus = info.Ride->status; info.DefaultStatus = WindowRideGetNextDefaultStatus(info.Ride); WindowRideSetDropdown(info, RideStatus::Closed, STR_CLOSE_RIDE); #ifdef __SIMULATE_IN_RIDE_WINDOW__ window_ride_set_dropdown(info, RideStatus::Simulating, STR_SIMULATE_RIDE); #endif WindowRideSetDropdown(info, RideStatus::Testing, STR_TEST_RIDE); WindowRideSetDropdown(info, RideStatus::Open, STR_OPEN_RIDE); WindowDropdownShowText( { w->windowPos.x + widget->left, w->windowPos.y + widget->top }, widget->height() + 1, w->colours[1], 0, info.NumItems); Dropdown::SetChecked(info.CheckedIndex, true); gDropdownDefaultIndex = info.DefaultIndex; } static StringId GetRideTypeNameForDropdown(ride_type_t rideType) { switch (rideType) { case RIDE_TYPE_1D: return STR_RIDE_NAME_1D; case RIDE_TYPE_1F: return STR_RIDE_NAME_1F; case RIDE_TYPE_22: return STR_RIDE_NAME_22; case RIDE_TYPE_50: return STR_RIDE_NAME_50; case RIDE_TYPE_52: return STR_RIDE_NAME_52; case RIDE_TYPE_53: return STR_RIDE_NAME_53; case RIDE_TYPE_54: return STR_RIDE_NAME_54; case RIDE_TYPE_55: return STR_RIDE_NAME_55; case RIDE_TYPE_59: return STR_RIDE_NAME_59; default: return GetRideTypeDescriptor(rideType).Naming.Name; } } static void PopulateRideTypeDropdown() { auto& ls = OpenRCT2::GetContext()->GetLocalisationService(); if (RideDropdownDataLanguage == ls.GetCurrentLanguage()) return; RideDropdownData.clear(); for (uint8_t i = 0; i < RIDE_TYPE_COUNT; i++) { auto name = GetRideTypeNameForDropdown(i); RideDropdownData.push_back({ i, name, ls.GetString(name) }); } std::sort(RideDropdownData.begin(), RideDropdownData.end(), [](auto& a, auto& b) { return String::Compare(a.label_string, b.label_string, true) < 0; }); RideDropdownDataLanguage = ls.GetCurrentLanguage(); } static void WindowRideShowRideTypeDropdown(rct_window* w, rct_widget* widget) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; PopulateRideTypeDropdown(); for (size_t i = 0; i < RideDropdownData.size(); i++) { gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[i].Args = RideDropdownData[i].label_id; } rct_widget* dropdownWidget = widget - 1; WindowDropdownShowText( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], Dropdown::Flag::StayOpen, RIDE_TYPE_COUNT); // Find the current ride type in the ordered list. int32_t pos = 0; for (int32_t i = 0; i < RIDE_TYPE_COUNT; i++) { if (RideDropdownData[i].ride_type_id == ride->type) { pos = i; break; } } gDropdownHighlightedIndex = pos; gDropdownDefaultIndex = pos; Dropdown::SetChecked(pos, true); } static void WindowRideShowLocateDropdown(rct_window* w, rct_widget* widget) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; gDropdownItems[0].Format = STR_LOCATE_SUBJECT_TIP; gDropdownItems[1].Format = STR_FOLLOW_SUBJECT_TIP; WindowDropdownShowText( { w->windowPos.x + widget->left, w->windowPos.y + widget->top }, widget->height() + 1, w->colours[1], 0, 2); gDropdownDefaultIndex = 0; if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK) || w->ride.view == 0 || w->ride.view > ride->NumTrains) { // Disable if we're a flat ride, 'overall view' is selected or a station is selected Dropdown::SetDisabled(1, true); } } static void WindowRideMainFollowRide(rct_window* w) { auto* ride = get_ride(w->rideId); if (ride != nullptr) { if (!(ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_MAIN)) { if (w->ride.view > 0) { if (w->ride.view <= ride->NumTrains) { Vehicle* vehicle = GetEntity(ride->vehicles[w->ride.view - 1]); if (vehicle != nullptr) { auto headVehicleSpriteIndex = vehicle->sprite_index; rct_window* w_main = window_get_main(); window_follow_sprite(*w_main, headVehicleSpriteIndex); } } } } } } static void PopulateVehicleTypeDropdown(Ride* ride, bool forceRefresh) { auto& objManager = GetContext()->GetObjectManager(); rct_ride_entry* rideEntry = ride->GetRideEntry(); bool selectionShouldBeExpanded; int32_t rideTypeIterator, rideTypeIteratorMax; if (gCheatsShowVehiclesFromOtherTrackTypes && !( ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE) || ride->type == RIDE_TYPE_MAZE || ride->type == RIDE_TYPE_MINI_GOLF)) { selectionShouldBeExpanded = true; rideTypeIterator = 0; rideTypeIteratorMax = RIDE_TYPE_COUNT - 1; } else { selectionShouldBeExpanded = false; rideTypeIterator = ride->type; rideTypeIteratorMax = ride->type; } // Don't repopulate the list if we just did. auto& ls = OpenRCT2::GetContext()->GetLocalisationService(); if (!forceRefresh && VehicleDropdownExpanded == selectionShouldBeExpanded && VehicleDropdownRideType == rideEntry && VehicleDropdownDataLanguage == ls.GetCurrentLanguage()) return; VehicleDropdownData.clear(); for (; rideTypeIterator <= rideTypeIteratorMax; rideTypeIterator++) { if (selectionShouldBeExpanded && GetRideTypeDescriptor(rideTypeIterator).HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE)) continue; if (selectionShouldBeExpanded && (rideTypeIterator == RIDE_TYPE_MAZE || rideTypeIterator == RIDE_TYPE_MINI_GOLF)) continue; auto& rideEntries = objManager.GetAllRideEntries(rideTypeIterator); for (auto rideEntryIndex : rideEntries) { const auto* currentRideEntry = get_ride_entry(rideEntryIndex); if (currentRideEntry == nullptr) continue; // Skip if vehicle type has not been invented yet if (!ride_entry_is_invented(rideEntryIndex) && !gCheatsIgnoreResearchStatus) continue; VehicleDropdownData.push_back( { rideEntryIndex, currentRideEntry->naming.Name, ls.GetString(currentRideEntry->naming.Name) }); } } std::sort(VehicleDropdownData.begin(), VehicleDropdownData.end(), [](auto& a, auto& b) { return String::Compare(a.label_string, b.label_string, true) < 0; }); VehicleDropdownExpanded = selectionShouldBeExpanded; VehicleDropdownRideType = rideEntry; VehicleDropdownDataLanguage = ls.GetCurrentLanguage(); } static void WindowRideShowVehicleTypeDropdown(rct_window* w, rct_widget* widget) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; PopulateVehicleTypeDropdown(ride); size_t numItems = std::min(VehicleDropdownData.size(), Dropdown::ItemsMaxSize); for (size_t i = 0; i < numItems; i++) { gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[i].Args = VehicleDropdownData[i].label_id; } rct_widget* dropdownWidget = widget - 1; WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, Dropdown::Flag::StayOpen, numItems, widget->right - dropdownWidget->left); // Find the current vehicle type in the ordered list. int32_t pos = 0; for (int32_t i = 0; i < static_cast(VehicleDropdownData.size()); i++) { if (VehicleDropdownData[i].subtype_id == ride->subtype) { pos = i; break; } } gDropdownHighlightedIndex = pos; gDropdownDefaultIndex = pos; Dropdown::SetChecked(pos, true); } /** * * rct2: 0x006AF1BD */ static void WindowRideMainMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget) { switch (widgetIndex) { case WIDX_VIEW_DROPDOWN: WindowRideShowViewDropdown(w, widget); break; case WIDX_OPEN: WindowRideShowOpenDropdown(w, widget); break; case WIDX_RIDE_TYPE_DROPDOWN: WindowRideShowRideTypeDropdown(w, widget); break; case WIDX_LOCATE: WindowRideShowLocateDropdown(w, widget); break; } } /** * * rct2: 0x006AF300 */ static void WindowRideMainDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex) { switch (widgetIndex) { case WIDX_VIEW_DROPDOWN: if (dropdownIndex == -1) { dropdownIndex = w->ride.view + 1; auto ride = get_ride(w->rideId); if (ride != nullptr) { if (dropdownIndex != 0 && dropdownIndex <= ride->NumTrains && !(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)) { dropdownIndex = ride->NumTrains + 1; } if (dropdownIndex >= gDropdownNumItems) { dropdownIndex = 0; } } } w->ride.view = dropdownIndex; WindowRideInitViewport(w); w->Invalidate(); break; case WIDX_OPEN: { auto ride = get_ride(w->rideId); if (ride != nullptr) { auto status = RideStatus::Closed; if (dropdownIndex < 0) { dropdownIndex = gDropdownHighlightedIndex; } if (dropdownIndex < static_cast(std::size(gDropdownItems))) { switch (gDropdownItems[dropdownIndex].Args) { case STR_CLOSE_RIDE: status = RideStatus::Closed; break; case STR_SIMULATE_RIDE: status = RideStatus::Simulating; break; case STR_TEST_RIDE: status = RideStatus::Testing; break; case STR_OPEN_RIDE: status = RideStatus::Open; break; } } auto gameAction = RideSetStatusAction(ride->id, status); GameActions::Execute(&gameAction); } break; } case WIDX_RIDE_TYPE_DROPDOWN: if (dropdownIndex != -1 && dropdownIndex < RIDE_TYPE_COUNT) { auto rideLabelId = std::clamp(dropdownIndex, 0, RIDE_TYPE_COUNT - 1); auto rideType = RideDropdownData[rideLabelId].ride_type_id; if (rideType < RIDE_TYPE_COUNT) { auto rideSetSetting = RideSetSettingAction(w->rideId, RideSetSetting::RideType, rideType); rideSetSetting.SetCallback([](const GameAction* ga, const GameActions::Result* result) { // Reset ghost track if ride construction window is open, prevents a crash // Will get set to the correct Alternative variable during set_default_next_piece. // TODO: Rework construction window to prevent the need for this. _currentTrackAlternative = RIDE_TYPE_NO_ALTERNATIVES; ride_construction_set_default_next_piece(); }); GameActions::Execute(&rideSetSetting); } } break; case WIDX_LOCATE: { if (dropdownIndex == 0) { w->ScrollToViewport(); } else if (dropdownIndex == 1) { WindowRideMainFollowRide(w); } break; } } } /** * * rct2: 0x006AF40F */ static void WindowRideMainUpdate(rct_window* w) { // Update tab animation w->frame_no++; window_event_invalidate_call(w); widget_invalidate(*w, WIDX_TAB_1); // Update status auto ride = get_ride(w->rideId); if (ride != nullptr) { if (!(ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_MAIN)) { if (w->ride.view == 0) return; if (w->ride.view <= ride->NumTrains) { Vehicle* vehicle = GetEntity(ride->vehicles[w->ride.view - 1]); if (vehicle == nullptr || (vehicle->status != Vehicle::Status::Travelling && vehicle->status != Vehicle::Status::TravellingCableLift && vehicle->status != Vehicle::Status::TravellingDodgems && vehicle->status != Vehicle::Status::TravellingBoat)) { return; } } } ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_MAIN; } widget_invalidate(*w, WIDX_STATUS); } /** * * rct2: 0x006AF2F9 */ static void WindowRideMainTextinput(rct_window* w, WidgetIndex widgetIndex, char* text) { if (widgetIndex != WIDX_RENAME || text == nullptr) return; auto ride = get_ride(w->rideId); if (ride != nullptr) { auto gameAction = RideSetNameAction(ride->id, text); GameActions::Execute(&gameAction); } } /** * * rct2: 0x006AF55A */ static void WindowRideMainViewportRotate(rct_window* w) { WindowRideInitViewport(w); } /** * * rct2: 0x006AECF6 */ static void WindowRideMainInvalidate(rct_window* w) { rct_widget* widgets; int32_t i, height; widgets = window_ride_page_widgets[w->page]; if (w->widgets != widgets) { w->widgets = widgets; WindowInitScrollWidgets(*w); } WindowRideSetPressedTab(w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; w->disabled_widgets &= ~((1ULL << WIDX_DEMOLISH) | (1ULL << WIDX_CONSTRUCTION)); if (ride->lifecycle_flags & (RIDE_LIFECYCLE_INDESTRUCTIBLE | RIDE_LIFECYCLE_INDESTRUCTIBLE_TRACK)) w->disabled_widgets |= (1ULL << WIDX_DEMOLISH); auto ft = Formatter::Common(); ride->FormatNameTo(ft); uint32_t spriteIds[] = { SPR_CLOSED, SPR_OPEN, SPR_TESTING, SPR_G2_SIMULATE, }; window_ride_main_widgets[WIDX_OPEN].image = spriteIds[EnumValue(ride->status)]; #ifdef __SIMULATE_IN_RIDE_WINDOW__ window_ride_main_widgets[WIDX_CLOSE_LIGHT].image = SPR_G2_RCT1_CLOSE_BUTTON_0 + (ride->status == RideStatus::Closed) * 2 + WidgetIsPressed(*w, WIDX_CLOSE_LIGHT); window_ride_main_widgets[WIDX_SIMULATE_LIGHT].image = SPR_G2_RCT1_SIMULATE_BUTTON_0 + (ride->status == RideStatus::Simulating) * 2 + WidgetIsPressed(*w, WIDX_SIMULATE_LIGHT); window_ride_main_widgets[WIDX_TEST_LIGHT].image = SPR_G2_RCT1_TEST_BUTTON_0 + (ride->status == RideStatus::Testing) * 2 + WidgetIsPressed(*w, WIDX_TEST_LIGHT); #else window_ride_main_widgets[WIDX_CLOSE_LIGHT].image = SPR_G2_RCT1_CLOSE_BUTTON_0 + (ride->status == RideStatus::Closed) * 2 + WidgetIsPressed(*w, WIDX_CLOSE_LIGHT); auto baseSprite = ride->status == RideStatus::Simulating ? SPR_G2_RCT1_SIMULATE_BUTTON_0 : SPR_G2_RCT1_TEST_BUTTON_0; window_ride_main_widgets[WIDX_TEST_LIGHT].image = baseSprite + (ride->status == RideStatus::Testing || ride->status == RideStatus::Simulating) * 2 + WidgetIsPressed(*w, WIDX_TEST_LIGHT); #endif window_ride_main_widgets[WIDX_OPEN_LIGHT].image = SPR_G2_RCT1_OPEN_BUTTON_0 + (ride->status == RideStatus::Open) * 2 + WidgetIsPressed(*w, WIDX_OPEN_LIGHT); WindowRideAnchorBorderWidgets(w); const int32_t offset = gCheatsAllowArbitraryRideTypeChanges ? 15 : 0; // Anchor main page specific widgets window_ride_main_widgets[WIDX_VIEWPORT].right = w->width - 26; window_ride_main_widgets[WIDX_VIEWPORT].bottom = w->height - (14 + offset); window_ride_main_widgets[WIDX_STATUS].right = w->width - 26; window_ride_main_widgets[WIDX_STATUS].top = w->height - (13 + offset); window_ride_main_widgets[WIDX_STATUS].bottom = w->height - (3 + offset); window_ride_main_widgets[WIDX_VIEW].right = w->width - 60; window_ride_main_widgets[WIDX_VIEW_DROPDOWN].right = w->width - 61; window_ride_main_widgets[WIDX_VIEW_DROPDOWN].left = w->width - 71; window_ride_main_widgets[WIDX_RIDE_TYPE].right = w->width - 26; window_ride_main_widgets[WIDX_RIDE_TYPE].top = w->height - 17; window_ride_main_widgets[WIDX_RIDE_TYPE].bottom = w->height - 4; window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].left = w->width - 37; window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].right = w->width - 27; window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].top = w->height - 16; window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].bottom = w->height - 5; if (!gCheatsAllowArbitraryRideTypeChanges) { window_ride_main_widgets[WIDX_RIDE_TYPE].type = WindowWidgetType::Empty; window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].type = WindowWidgetType::Empty; } else { window_ride_main_widgets[WIDX_RIDE_TYPE].type = WindowWidgetType::DropdownMenu; window_ride_main_widgets[WIDX_RIDE_TYPE].text = ride->GetRideTypeDescriptor().Naming.Name; window_ride_main_widgets[WIDX_RIDE_TYPE_DROPDOWN].type = WindowWidgetType::Button; } window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10); if (ThemeGetFlags() & UITHEME_FLAG_USE_LIGHTS_RIDE) { window_ride_main_widgets[WIDX_OPEN].type = WindowWidgetType::Empty; window_ride_main_widgets[WIDX_CLOSE_LIGHT].type = WindowWidgetType::ImgBtn; window_ride_main_widgets[WIDX_SIMULATE_LIGHT].type = WindowWidgetType::Empty; #ifdef __SIMULATE_IN_RIDE_WINDOW__ if (ride->SupportsStatus(RideStatus::Simulating)) window_ride_main_widgets[WIDX_SIMULATE_LIGHT].type = WindowWidgetType::ImgBtn; #endif window_ride_main_widgets[WIDX_TEST_LIGHT].type = ride->SupportsStatus(RideStatus::Testing) ? WindowWidgetType::ImgBtn : WindowWidgetType::Empty; window_ride_main_widgets[WIDX_OPEN_LIGHT].type = WindowWidgetType::ImgBtn; height = 62; if (window_ride_main_widgets[WIDX_SIMULATE_LIGHT].type != WindowWidgetType::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 != WindowWidgetType::Empty) { window_ride_main_widgets[WIDX_TEST_LIGHT].top = height; window_ride_main_widgets[WIDX_TEST_LIGHT].bottom = height + 13; height += 14; } window_ride_main_widgets[WIDX_OPEN_LIGHT].top = height; window_ride_main_widgets[WIDX_OPEN_LIGHT].bottom = height + 13; height += 14 - 24 + RCT1_LIGHT_OFFSET; } else { window_ride_main_widgets[WIDX_OPEN].type = WindowWidgetType::FlatBtn; window_ride_main_widgets[WIDX_CLOSE_LIGHT].type = WindowWidgetType::Empty; window_ride_main_widgets[WIDX_SIMULATE_LIGHT].type = WindowWidgetType::Empty; window_ride_main_widgets[WIDX_TEST_LIGHT].type = WindowWidgetType::Empty; window_ride_main_widgets[WIDX_OPEN_LIGHT].type = WindowWidgetType::Empty; height = 46; } for (i = WIDX_CLOSE_LIGHT; i <= WIDX_OPEN_LIGHT; i++) { window_ride_main_widgets[i].left = w->width - 20; window_ride_main_widgets[i].right = w->width - 7; } for (i = WIDX_OPEN; i <= WIDX_DEMOLISH; i++, height += 24) { window_ride_main_widgets[i].left = w->width - 25; window_ride_main_widgets[i].right = w->width - 2; window_ride_main_widgets[i].top = height; window_ride_main_widgets[i].bottom = height + 23; } } /** * * rct2: 0x006AF10A */ static StringId WindowRideGetStatusOverallView(rct_window* w, Formatter& ft) { auto stringId = STR_NONE; auto ride = get_ride(w->rideId); if (ride != nullptr) { ride->FormatStatusTo(ft); stringId = STR_BLACK_STRING; if ((ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) || (ride->lifecycle_flags & RIDE_LIFECYCLE_CRASHED)) { stringId = STR_RED_OUTLINED_STRING; } } return stringId; } /** * * rct2: 0x006AEFEF */ static StringId WindowRideGetStatusVehicle(rct_window* w, Formatter& ft) { auto ride = get_ride(w->rideId); if (ride == nullptr) return STR_EMPTY; auto vehicle = GetEntity(ride->vehicles[w->ride.view - 1]); if (vehicle == nullptr) return STR_EMPTY; if (vehicle->status != Vehicle::Status::Crashing && vehicle->status != Vehicle::Status::Crashed) { auto trackType = vehicle->GetTrackType(); if (trackType == TrackElemType::BlockBrakes || trackType == TrackElemType::CableLiftHill || trackType == TrackElemType::Up25ToFlat || trackType == TrackElemType::Up60ToFlat || trackType == TrackElemType::DiagUp25ToFlat || trackType == TrackElemType::DiagUp60ToFlat) { if (ride->GetRideTypeDescriptor().SupportsTrackPiece(TRACK_BLOCK_BRAKES) && vehicle->velocity == 0) { ft.Add(STR_STOPPED_BY_BLOCK_BRAKES); return STR_BLACK_STRING; } } } if (ride->type == RIDE_TYPE_MINI_GOLF) return STR_EMPTY; auto stringId = VehicleStatusNames[static_cast(vehicle->status)]; if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_SINGLE_SESSION) && vehicle->status <= Vehicle::Status::UnloadingPassengers) { stringId = SingleSessionVehicleStatusNames[static_cast(vehicle->status)]; } ft.Add(stringId); uint16_t speedInMph = (abs(vehicle->velocity) * 9) >> 18; ft.Add(speedInMph); const RideComponentName stationName = GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.station); ft.Add(ride->num_stations > 1 ? stationName.number : stationName.singular); ft.Add(vehicle->current_station.ToUnderlying() + 1); return stringId != STR_CRASHING && stringId != STR_CRASHED_0 ? STR_BLACK_STRING : STR_RED_OUTLINED_STRING; } /** * * rct2: 0x006AEF65 */ static StringId WindowRideGetStatusStation(rct_window* w, Formatter& ft) { auto ride = get_ride(w->rideId); if (ride == nullptr) return STR_NONE; const auto stationIndex = GetStationIndexFromViewSelection(*w); if (!stationIndex) { return STR_NONE; } const auto& station = ride->GetStation(*stationIndex); StringId stringId = STR_EMPTY; // Entrance / exit if (ride->status == RideStatus::Closed) { if (station.Entrance.IsNull()) stringId = STR_NO_ENTRANCE; else if (station.Exit.IsNull()) stringId = STR_NO_EXIT; } else { if (station.Entrance.IsNull()) stringId = STR_EXIT_ONLY; } // Queue length if (stringId == STR_EMPTY) { stringId = STR_QUEUE_EMPTY; uint16_t queueLength = ride->GetStation(*stationIndex).QueueLength; if (queueLength == 1) stringId = STR_QUEUE_ONE_PERSON; else if (queueLength > 1) stringId = STR_QUEUE_PEOPLE; ft.Add(stringId); ft.Add(queueLength); } else { ft.Add(stringId); } return STR_BLACK_STRING; } /** * * rct2: 0x006AEE73 */ static StringId WindowRideGetStatus(rct_window* w, Formatter& ft) { auto ride = get_ride(w->rideId); if (w->ride.view == 0) return WindowRideGetStatusOverallView(w, ft); if (ride != nullptr && w->ride.view <= ride->NumTrains) return WindowRideGetStatusVehicle(w, ft); if (ride != nullptr && ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) return WindowRideGetStatusOverallView(w, ft); return WindowRideGetStatusStation(w, ft); } /** * * rct2: 0x006AEE73 */ static void WindowRideMainPaint(rct_window* w, rct_drawpixelinfo* dpi) { rct_widget* widget; WindowDrawWidgets(*w, dpi); WindowRideDrawTabImages(dpi, w); // Viewport and ear icon if (w->viewport != nullptr) { window_draw_viewport(dpi, *w); if (w->viewport->flags & VIEWPORT_FLAG_SOUND_ON) gfx_draw_sprite(dpi, ImageId(SPR_HEARING_VIEWPORT), w->windowPos + ScreenCoordsXY{ 2, 2 }); } // View dropdown auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto ft = Formatter(); if (w->ride.view != 0) { if (w->ride.view > ride->NumTrains) { ft.Add(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.station).number); ft.Add(w->ride.view - ride->NumTrains); } else { ft.Add(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).number); ft.Add(w->ride.view); } } else { ft.Add(STR_OVERALL_VIEW); } widget = &window_ride_main_widgets[WIDX_VIEW]; DrawTextBasic( dpi, { w->windowPos.x + (widget->left + widget->right - 11) / 2, w->windowPos.y + widget->top }, STR_WINDOW_COLOUR_2_STRINGID, ft, { TextAlignment::CENTRE }); // Status ft = Formatter(); widget = &window_ride_main_widgets[WIDX_STATUS]; StringId rideStatus = WindowRideGetStatus(w, ft); DrawTextEllipsised( dpi, w->windowPos + ScreenCoordsXY{ (widget->left + widget->right) / 2, widget->top }, widget->width(), rideStatus, ft, { TextAlignment::CENTRE }); } #pragma endregion #pragma region Vehicle /** * * rct2: 0x006B272D */ static void WindowRideVehicleMouseup(rct_window* w, WidgetIndex widgetIndex) { switch (widgetIndex) { case WIDX_CLOSE: window_close(*w); break; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: case WIDX_TAB_5: case WIDX_TAB_6: case WIDX_TAB_7: case WIDX_TAB_8: case WIDX_TAB_9: case WIDX_TAB_10: WindowRideSetPage(w, widgetIndex - WIDX_TAB_1); break; } } /** * * rct2: 0x006B2ABB */ static void WindowRideVehicleResize(rct_window* w) { window_set_resize(*w, 316, 214, 316, 214); } /** * * rct2: 0x006B2748 */ static void WindowRideVehicleMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; switch (widgetIndex) { case WIDX_VEHICLE_TYPE_DROPDOWN: WindowRideShowVehicleTypeDropdown(w, &w->widgets[widgetIndex]); break; case WIDX_VEHICLE_TRAINS_INCREASE: if (ride->NumTrains < OpenRCT2::Limits::MaxTrainsPerRide) ride->SetNumTrains(ride->NumTrains + 1); break; case WIDX_VEHICLE_TRAINS_DECREASE: if (ride->NumTrains > 1) ride->SetNumTrains(ride->NumTrains - 1); break; case WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE: if (ride->num_cars_per_train < OpenRCT2::Limits::MaxCarsPerTrain) ride->SetNumCarsPerVehicle(ride->num_cars_per_train + 1); break; case WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE: rct_ride_entry* rideEntry = ride->GetRideEntry(); if (ride->num_cars_per_train > rideEntry->zero_cars + 1) ride->SetNumCarsPerVehicle(ride->num_cars_per_train - 1); break; } } /** * * rct2: 0x006B2767 */ static void WindowRideVehicleDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex) { if (dropdownIndex == -1) return; switch (widgetIndex) { case WIDX_VEHICLE_TYPE_DROPDOWN: if (dropdownIndex >= 0 && static_cast(dropdownIndex) < VehicleDropdownData.size()) { auto ride = get_ride(w->rideId); if (ride != nullptr) { auto newRideType = VehicleDropdownData[dropdownIndex].subtype_id; ride->SetRideEntry(newRideType); } } break; } } /** * * rct2: 0x006B2AA1 */ static void WindowRideVehicleUpdate(rct_window* w) { w->frame_no++; window_event_invalidate_call(w); widget_invalidate(*w, WIDX_TAB_2); } static OpenRCT2String WindowRideVehicleTooltip(rct_window* const w, const WidgetIndex widgetIndex, StringId fallback) { auto ride = get_ride(w->rideId); if (ride == nullptr) return { STR_NONE, {} }; switch (widgetIndex) { case WIDX_VEHICLE_TRAINS: case WIDX_VEHICLE_TRAINS_DECREASE: case WIDX_VEHICLE_TRAINS_INCREASE: { auto ft = Formatter(); ft.Increment(12); RideComponentType vehicleType = ride->GetRideTypeDescriptor().NameConvention.vehicle; StringId stringId = GetRideComponentName(vehicleType).count; if (ride->max_trains > 1) { stringId = GetRideComponentName(vehicleType).count_plural; } ft.Add(stringId); ft.Add(ride->max_trains); return { fallback, ft }; } case WIDX_VEHICLE_CARS_PER_TRAIN: case WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE: case WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE: { auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr) return { STR_NONE, {} }; auto ft = Formatter(); ft.Increment(16); ft.Add(std::max(uint8_t(1), ride->MaxCarsPerTrain) - rideEntry->zero_cars); StringId stringId = GetRideComponentName(RideComponentType::Car).singular; if (ride->MaxCarsPerTrain - rideEntry->zero_cars > 1) { stringId = GetRideComponentName(RideComponentType::Car).plural; } ft.Add(stringId); return { fallback, ft }; } } return { fallback, {} }; } /** * * rct2: 0x006B222C */ static void WindowRideVehicleInvalidate(rct_window* w) { rct_widget* widgets; rct_ride_entry* rideEntry; StringId stringId; int32_t carsPerTrain; widgets = window_ride_page_widgets[w->page]; if (w->widgets != widgets) { w->widgets = widgets; WindowInitScrollWidgets(*w); } WindowRideSetPressedTab(w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; rideEntry = ride->GetRideEntry(); w->widgets[WIDX_TITLE].text = STR_ARG_20_STRINGID; // Widget setup carsPerTrain = ride->num_cars_per_train - rideEntry->zero_cars; // Vehicle type window_ride_vehicle_widgets[WIDX_VEHICLE_TYPE].text = rideEntry->naming.Name; // Trains if (rideEntry->cars_per_flat_ride > 1 || gCheatsDisableTrainLengthLimit) { window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS].type = WindowWidgetType::Spinner; window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS_INCREASE].type = WindowWidgetType::Button; window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS_DECREASE].type = WindowWidgetType::Button; } else { window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS].type = WindowWidgetType::Empty; window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS_INCREASE].type = WindowWidgetType::Empty; window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS_DECREASE].type = WindowWidgetType::Empty; } // Cars per train if (rideEntry->zero_cars + 1 < rideEntry->max_cars_in_train || gCheatsDisableTrainLengthLimit) { window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN].type = WindowWidgetType::Spinner; window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE].type = WindowWidgetType::Button; window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE].type = WindowWidgetType::Button; } else { window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN].type = WindowWidgetType::Empty; window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN_INCREASE].type = WindowWidgetType::Empty; window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN_DECREASE].type = WindowWidgetType::Empty; } auto ft = Formatter::Common(); ft.Increment(6); ft.Add(carsPerTrain); RideComponentType vehicleType = ride->GetRideTypeDescriptor().NameConvention.vehicle; stringId = GetRideComponentName(vehicleType).count; if (ride->NumTrains > 1) { stringId = GetRideComponentName(vehicleType).count_plural; } ft.Add(stringId); ft.Add(ride->NumTrains); ft.Increment(8); ride->FormatNameTo(ft); WindowRideAnchorBorderWidgets(w); window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10); if (ride->num_cars_per_train > (rideEntry->zero_cars + 1)) { window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN].text = STR_X_CARS_PER_TRAIN; } else { window_ride_vehicle_widgets[WIDX_VEHICLE_CARS_PER_TRAIN].text = STR_1_CAR_PER_TRAIN; } } /** * * rct2: 0x006B23DC */ static void WindowRideVehiclePaint(rct_window* w, rct_drawpixelinfo* dpi) { WindowDrawWidgets(*w, dpi); WindowRideDrawTabImages(dpi, w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr) return; auto screenCoords = w->windowPos + ScreenCoordsXY{ 8, 64 }; // Description auto ft = Formatter(); ft.Add(rideEntry->naming.Description); screenCoords.y += DrawTextWrapped(dpi, screenCoords, 300, STR_BLACK_STRING, ft, { TextAlignment::LEFT }); screenCoords.y += 2; // Capacity ft = Formatter(); ft.Add(rideEntry->capacity); DrawTextBasic(dpi, screenCoords, STR_CAPACITY, ft); // Excitement Factor if (rideEntry->excitement_multiplier > 0) { screenCoords.y += LIST_ROW_HEIGHT; ft = Formatter(); ft.Add(rideEntry->excitement_multiplier); DrawTextBasic(dpi, screenCoords, STR_EXCITEMENT_FACTOR, ft); } // Intensity Factor if (rideEntry->intensity_multiplier > 0) { int32_t lineHeight = font_get_line_height(FontSpriteBase::MEDIUM); if (lineHeight != 10) screenCoords.x += 150; else screenCoords.y += LIST_ROW_HEIGHT; ft = Formatter(); ft.Add(rideEntry->intensity_multiplier); DrawTextBasic(dpi, screenCoords, STR_INTENSITY_FACTOR, ft); if (lineHeight != 10) screenCoords.x -= 150; } // Nausea Factor if (rideEntry->nausea_multiplier > 0) { screenCoords.y += LIST_ROW_HEIGHT; ft = Formatter(); ft.Add(rideEntry->nausea_multiplier); DrawTextBasic(dpi, screenCoords, STR_NAUSEA_FACTOR, ft); } } struct VehicleDrawInfo { int16_t x; int16_t y; ImageId imageId; }; /** * * rct2: 0x006B2502 */ static void WindowRideVehicleScrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; rct_ride_entry* rideEntry = ride->GetRideEntry(); // Background gfx_fill_rect(dpi, { { dpi->x, dpi->y }, { dpi->x + dpi->width, dpi->y + dpi->height } }, PALETTE_INDEX_12); rct_widget* widget = &window_ride_vehicle_widgets[WIDX_VEHICLE_TRAINS_PREVIEW]; int32_t startX = std::max(2, (widget->width() - ((ride->NumTrains - 1) * 36)) / 2 - 25); int32_t startY = widget->height() - 4; CarEntry* carEntry = &rideEntry->Cars[ride_entry_get_vehicle_at_position(ride->subtype, ride->num_cars_per_train, 0)]; startY += carEntry->tab_height; // For each train for (int32_t i = 0; i < ride->NumTrains; i++) { VehicleDrawInfo trainCarImages[OpenRCT2::Limits::MaxCarsPerTrain]; VehicleDrawInfo* nextSpriteToDraw = trainCarImages; int32_t x = startX; int32_t y = startY; // For each car in train static_assert(std::numeric_limitsnum_cars_per_train)>::max() <= std::size(trainCarImages)); for (int32_t j = 0; j < ride->num_cars_per_train; j++) { carEntry = &rideEntry->Cars[ride_entry_get_vehicle_at_position(ride->subtype, ride->num_cars_per_train, j)]; x += carEntry->spacing / 17432; y -= (carEntry->spacing / 2) / 17432; // Get colour of vehicle int32_t vehicleColourIndex = 0; switch (ride->colour_scheme_type & 3) { case VEHICLE_COLOUR_SCHEME_SAME: vehicleColourIndex = 0; break; case VEHICLE_COLOUR_SCHEME_PER_TRAIN: vehicleColourIndex = i; break; case VEHICLE_COLOUR_SCHEME_PER_VEHICLE: vehicleColourIndex = j; break; } VehicleColour vehicleColour = ride_get_vehicle_colour(ride, vehicleColourIndex); ImageIndex imageIndex = carEntry->SpriteByYaw(OpenRCT2::Entity::Yaw::BaseRotation / 2, SpriteGroupType::SlopeFlat); imageIndex &= carEntry->TabRotationMask; imageIndex *= carEntry->base_num_frames; imageIndex += carEntry->base_image_id; auto imageId = ImageId(imageIndex, vehicleColour.Body, vehicleColour.Trim, vehicleColour.Tertiary); nextSpriteToDraw->x = x; nextSpriteToDraw->y = y; nextSpriteToDraw->imageId = imageId; nextSpriteToDraw++; x += carEntry->spacing / 17432; y -= (carEntry->spacing / 2) / 17432; } if (ride->type == RIDE_TYPE_REVERSER_ROLLER_COASTER) { VehicleDrawInfo tmp = *(nextSpriteToDraw - 1); *(nextSpriteToDraw - 1) = *(nextSpriteToDraw - 2); *(nextSpriteToDraw - 2) = tmp; } VehicleDrawInfo* current = nextSpriteToDraw; while (--current >= trainCarImages) gfx_draw_sprite(dpi, current->imageId, { current->x, current->y }); startX += 36; } } #pragma endregion #pragma region Operating /** * * rct2: 0x006B11D5 */ static void WindowRideModeTweakIncrease(rct_window* w) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; const auto& operatingSettings = ride->GetRideTypeDescriptor().OperatingSettings; uint8_t maxValue = operatingSettings.MaxValue; uint8_t minValue = gCheatsUnlockOperatingLimits ? 0 : operatingSettings.MinValue; if (gCheatsUnlockOperatingLimits) { maxValue = OpenRCT2::Limits::CheatsMaxOperatingLimit; } uint8_t increment = ride->mode == RideMode::Dodgems ? 10 : 1; set_operating_setting( w->rideId, RideSetSetting::Operation, std::clamp(ride->operation_option + increment, minValue, maxValue)); } /** * * rct2: 0x006B120A */ static void WindowRideModeTweakDecrease(rct_window* w) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; const auto& operatingSettings = ride->GetRideTypeDescriptor().OperatingSettings; uint8_t maxValue = operatingSettings.MaxValue; uint8_t minValue = gCheatsUnlockOperatingLimits ? 0 : operatingSettings.MinValue; if (gCheatsUnlockOperatingLimits) { maxValue = OpenRCT2::Limits::CheatsMaxOperatingLimit; } uint8_t decrement = ride->mode == RideMode::Dodgems ? 10 : 1; set_operating_setting( w->rideId, RideSetSetting::Operation, std::clamp(ride->operation_option - decrement, minValue, maxValue)); } /** * * rct2: 0x006B1631 */ static void WindowRideModeDropdown(rct_window* w, rct_widget* widget) { rct_widget* dropdownWidget; dropdownWidget = widget - 1; auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto availableModes = ride->GetAvailableModes(); // Create dropdown list auto numAvailableModes = 0; auto checkedIndex = -1; for (auto i = 0; i < static_cast(RideMode::Count); i++) { if (availableModes & (1ULL << i)) { gDropdownItems[numAvailableModes].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[numAvailableModes].Args = RideModeNames[i]; if (ride->mode == static_cast(i)) checkedIndex = numAvailableModes; numAvailableModes++; } } WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, Dropdown::Flag::StayOpen, numAvailableModes, widget->right - dropdownWidget->left); if (checkedIndex != -1) { Dropdown::SetChecked(checkedIndex, true); } } /** * * rct2: 0x006B15C0 */ static void WindowRideLoadDropdown(rct_window* w, rct_widget* widget) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto dropdownWidget = widget - 1; for (auto i = 0; i < 5; i++) { gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[i].Args = VehicleLoadNames[i]; } WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, Dropdown::Flag::StayOpen, 5, widget->right - dropdownWidget->left); Dropdown::SetChecked(ride->depart_flags & RIDE_DEPART_WAIT_FOR_LOAD_MASK, true); } /** * * rct2: 0x006B10A7 */ static void WindowRideOperatingMouseup(rct_window* w, WidgetIndex widgetIndex) { const auto rideId = w->rideId; auto ride = get_ride(rideId); if (ride == nullptr) return; switch (widgetIndex) { case WIDX_CLOSE: window_close(*w); break; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: case WIDX_TAB_5: case WIDX_TAB_6: case WIDX_TAB_7: case WIDX_TAB_8: case WIDX_TAB_9: case WIDX_TAB_10: WindowRideSetPage(w, widgetIndex - WIDX_TAB_1); break; case WIDX_LOAD_CHECKBOX: set_operating_setting(rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_WAIT_FOR_LOAD); break; case WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX: set_operating_setting( rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_LEAVE_WHEN_ANOTHER_ARRIVES); break; case WIDX_MINIMUM_LENGTH_CHECKBOX: set_operating_setting(rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_WAIT_FOR_MINIMUM_LENGTH); break; case WIDX_MAXIMUM_LENGTH_CHECKBOX: set_operating_setting(rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_WAIT_FOR_MAXIMUM_LENGTH); break; case WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX: set_operating_setting( rideId, RideSetSetting::Departure, ride->depart_flags ^ RIDE_DEPART_SYNCHRONISE_WITH_ADJACENT_STATIONS); break; } } /** * * rct2: 0x006B1715 */ static void WindowRideOperatingResize(rct_window* w) { window_set_resize(*w, 316, 186, 316, 186); } /** * * rct2: 0x006B10F4 */ static void WindowRideOperatingMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget) { const auto rideId = w->rideId; auto ride = get_ride(rideId); if (ride == nullptr) return; uint8_t upper_bound, lower_bound; switch (widgetIndex) { case WIDX_MODE_TWEAK: WindowRideOperatingTweakTextInput(w, *ride); break; case WIDX_MODE_TWEAK_INCREASE: WindowRideModeTweakIncrease(w); break; case WIDX_MODE_TWEAK_DECREASE: WindowRideModeTweakDecrease(w); break; case WIDX_LIFT_HILL_SPEED_INCREASE: upper_bound = gCheatsUnlockOperatingLimits ? OpenRCT2::Limits::CheatsMaxOperatingLimit : ride->GetRideTypeDescriptor().LiftData.maximum_speed; lower_bound = gCheatsUnlockOperatingLimits ? 0 : ride->GetRideTypeDescriptor().LiftData.minimum_speed; set_operating_setting( rideId, RideSetSetting::LiftHillSpeed, std::clamp(ride->lift_hill_speed + 1, lower_bound, upper_bound)); break; case WIDX_LIFT_HILL_SPEED_DECREASE: upper_bound = gCheatsUnlockOperatingLimits ? OpenRCT2::Limits::CheatsMaxOperatingLimit : ride->GetRideTypeDescriptor().LiftData.maximum_speed; lower_bound = gCheatsUnlockOperatingLimits ? 0 : ride->GetRideTypeDescriptor().LiftData.minimum_speed; set_operating_setting( rideId, RideSetSetting::LiftHillSpeed, std::clamp(ride->lift_hill_speed - 1, lower_bound, upper_bound)); break; case WIDX_MINIMUM_LENGTH: WindowRideOperatingLengthWindow(w, WIDX_MINIMUM_LENGTH); break; case WIDX_MAXIMUM_LENGTH: WindowRideOperatingLengthWindow(w, WIDX_MAXIMUM_LENGTH); break; case WIDX_MINIMUM_LENGTH_INCREASE: upper_bound = OpenRCT2::Limits::MaxWaitingTime; lower_bound = 0; set_operating_setting( rideId, RideSetSetting::MinWaitingTime, std::clamp(ride->min_waiting_time + 1, lower_bound, upper_bound)); break; case WIDX_MINIMUM_LENGTH_DECREASE: upper_bound = OpenRCT2::Limits::MaxWaitingTime; lower_bound = 0; set_operating_setting( rideId, RideSetSetting::MinWaitingTime, std::clamp(ride->min_waiting_time - 1, lower_bound, upper_bound)); break; case WIDX_MAXIMUM_LENGTH_INCREASE: upper_bound = OpenRCT2::Limits::MaxWaitingTime; lower_bound = 0; set_operating_setting( rideId, RideSetSetting::MaxWaitingTime, std::clamp(ride->max_waiting_time + 1, lower_bound, upper_bound)); break; case WIDX_MAXIMUM_LENGTH_DECREASE: upper_bound = OpenRCT2::Limits::MaxWaitingTime; lower_bound = 0; set_operating_setting( rideId, RideSetSetting::MaxWaitingTime, std::clamp(ride->max_waiting_time - 1, lower_bound, upper_bound)); break; case WIDX_MODE_DROPDOWN: WindowRideModeDropdown(w, widget); break; case WIDX_LOAD_DROPDOWN: WindowRideLoadDropdown(w, widget); break; case WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE: upper_bound = gCheatsUnlockOperatingLimits ? OpenRCT2::Limits::CheatsMaxOperatingLimit : OpenRCT2::Limits::MaxCircuitsPerRide; lower_bound = 1; set_operating_setting( rideId, RideSetSetting::NumCircuits, std::clamp(ride->num_circuits + 1, lower_bound, upper_bound)); break; case WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE: upper_bound = gCheatsUnlockOperatingLimits ? OpenRCT2::Limits::CheatsMaxOperatingLimit : OpenRCT2::Limits::MaxCircuitsPerRide; lower_bound = 1; set_operating_setting( rideId, RideSetSetting::NumCircuits, std::clamp(ride->num_circuits - 1, lower_bound, upper_bound)); break; } } static void WindowRideOperatingLengthWindow(rct_window* w, WidgetIndex widgetIndex) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; uint8_t upperBound = OpenRCT2::Limits::MaxWaitingTime; uint8_t lowerBound = 0; Formatter ft; ft.Add(lowerBound); ft.Add(upperBound); auto title = (widgetIndex == WIDX_MINIMUM_LENGTH) ? STR_MINIMUM_WAITING_TIME : STR_MAXIMUM_WAITING_TIME; auto currentValue = (widgetIndex == WIDX_MINIMUM_LENGTH) ? ride->min_waiting_time : ride->max_waiting_time; char buffer[5]{}; snprintf(buffer, std::size(buffer), "%u", currentValue); WindowTextInputRawOpen(w, widgetIndex, title, STR_ENTER_VALUE, ft, buffer, 4); } static void WindowRideOperatingTweakTextInput(rct_window* w, const Ride& ride) { switch (ride.mode) { case RideMode::PoweredLaunchPasstrough: case RideMode::PoweredLaunch: case RideMode::UpwardLaunch: case RideMode::PoweredLaunchBlockSectioned: case RideMode::StationToStation: case RideMode::Dodgems: return; default: break; } const auto& operatingSettings = ride.GetRideTypeDescriptor().OperatingSettings; int16_t maxValue = gCheatsUnlockOperatingLimits ? OpenRCT2::Limits::CheatsMaxOperatingLimit : operatingSettings.MaxValue; int16_t minValue = gCheatsUnlockOperatingLimits ? 0 : operatingSettings.MinValue; const auto& title = window_ride_operating_widgets[WIDX_MODE_TWEAK_LABEL].text; Formatter ft; ft.Add(minValue * operatingSettings.OperatingSettingMultiplier); ft.Add(maxValue * operatingSettings.OperatingSettingMultiplier); uint16_t currentValue = static_cast(ride.operation_option) * operatingSettings.OperatingSettingMultiplier; char buffer[6]{}; snprintf(buffer, std::size(buffer), "%u", currentValue); WindowTextInputRawOpen(w, WIDX_MODE_TWEAK, title, STR_ENTER_VALUE, ft, buffer, 4); } /** * * rct2: 0x006B1165 */ static void WindowRideOperatingDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex) { if (dropdownIndex == -1) return; const auto rideId = w->rideId; auto ride = get_ride(rideId); if (ride == nullptr) return; switch (widgetIndex) { case WIDX_MODE_DROPDOWN: { RideMode rideMode = RideMode::NullMode; auto availableModes = ride->GetAvailableModes(); auto modeInDropdownIndex = -1; for (RideMode rideModeIndex = RideMode::Normal; rideModeIndex < RideMode::Count; rideModeIndex++) { if (availableModes & EnumToFlag(rideModeIndex)) { modeInDropdownIndex++; if (modeInDropdownIndex == dropdownIndex) { rideMode = rideModeIndex; break; } } } if (rideMode != RideMode::NullMode) set_operating_setting(rideId, RideSetSetting::Mode, static_cast(rideMode)); break; } case WIDX_LOAD_DROPDOWN: set_operating_setting( rideId, RideSetSetting::Departure, (ride->depart_flags & ~RIDE_DEPART_WAIT_FOR_LOAD_MASK) | dropdownIndex); break; } } /** * * rct2: 0x006B178E */ static void WindowRideOperatingUpdate(rct_window* w) { w->frame_no++; window_event_invalidate_call(w); widget_invalidate(*w, WIDX_TAB_3); auto ride = get_ride(w->rideId); if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_OPERATING) { ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_OPERATING; w->Invalidate(); } } static void WindowRideOperatingTextinput(rct_window* w, WidgetIndex widgetIndex, char* text) { if (text == nullptr) return; auto ride = get_ride(w->rideId); if (ride == nullptr) return; if (widgetIndex == WIDX_MODE_TWEAK) { const auto& operatingSettings = ride->GetRideTypeDescriptor().OperatingSettings; uint32_t maxValue = gCheatsUnlockOperatingLimits ? OpenRCT2::Limits::CheatsMaxOperatingLimit : operatingSettings.MaxValue; uint32_t minValue = gCheatsUnlockOperatingLimits ? 0 : operatingSettings.MinValue; auto multiplier = ride->GetRideTypeDescriptor().OperatingSettings.OperatingSettingMultiplier; try { uint32_t origSize = std::stol(std::string(text)) / multiplier; uint8_t size = static_cast(std::clamp(origSize, minValue, maxValue)); set_operating_setting(ride->id, RideSetSetting::Operation, size); } catch (const std::logic_error&) { // std::stol can throw std::out_of_range or std::invalid_argument } } else if (widgetIndex == WIDX_MINIMUM_LENGTH || widgetIndex == WIDX_MAXIMUM_LENGTH) { try { auto rideSetSetting = widgetIndex == WIDX_MINIMUM_LENGTH ? RideSetSetting::MinWaitingTime : RideSetSetting::MaxWaitingTime; uint16_t upperBound = OpenRCT2::Limits::MaxWaitingTime; uint16_t lowerBound = 0; uint16_t size = std::stol(std::string(text)); size = std::clamp(size, lowerBound, upperBound); set_operating_setting(ride->id, rideSetSetting, size); } catch (const std::logic_error&) { // std::stol can throw std::out_of_range or std::invalid_argument } } } /** * * rct2: 0x006B0B30 */ static void WindowRideOperatingInvalidate(rct_window* w) { rct_widget* widgets; StringId format, caption, tooltip; widgets = window_ride_page_widgets[w->page]; if (w->widgets != widgets) { w->widgets = widgets; WindowInitScrollWidgets(*w); } WindowRideSetPressedTab(w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto ft = Formatter::Common(); ride->FormatNameTo(ft); // Widget setup w->pressed_widgets &= ~( (1ULL << WIDX_LOAD_CHECKBOX) | (1ULL << WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX) | (1ULL << WIDX_MINIMUM_LENGTH_CHECKBOX) | (1ULL << WIDX_MAXIMUM_LENGTH_CHECKBOX) | (1ULL << WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX)); // Sometimes, only one of the alternatives support lift hill pieces. Make sure to check both. bool hasAlternativeType = ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_ALTERNATIVE_TRACK_TYPE); if (ride->GetRideTypeDescriptor().SupportsTrackPiece(TRACK_LIFT_HILL) || (hasAlternativeType && GetRideTypeDescriptor(ride->GetRideTypeDescriptor().AlternateType).SupportsTrackPiece(TRACK_LIFT_HILL))) { window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_LABEL].type = WindowWidgetType::Label; window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED].type = WindowWidgetType::Spinner; window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_INCREASE].type = WindowWidgetType::Button; window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_DECREASE].type = WindowWidgetType::Button; ft.Rewind(); ft.Increment(20); ft.Add(ride->lift_hill_speed); } else { window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_LABEL].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_INCREASE].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_LIFT_HILL_SPEED_DECREASE].type = WindowWidgetType::Empty; } // Number of circuits if (ride->CanHaveMultipleCircuits()) { window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_LABEL].type = WindowWidgetType::Label; window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS].type = WindowWidgetType::Spinner; window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE].type = WindowWidgetType::Button; window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE].type = WindowWidgetType::Button; ft.Rewind(); ft.Increment(22); ft.Add(ride->num_circuits); } else { window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_LABEL].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_INCREASE].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_OPERATE_NUMBER_OF_CIRCUITS_DECREASE].type = WindowWidgetType::Empty; } // Leave if another vehicle arrives at station if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LEAVE_WHEN_ANOTHER_VEHICLE_ARRIVES_AT_STATION) && ride->NumTrains > 1 && !ride->IsBlockSectioned()) { window_ride_operating_widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].type = WindowWidgetType::Checkbox; window_ride_operating_widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].tooltip = STR_LEAVE_IF_ANOTHER_VEHICLE_ARRIVES_TIP; window_ride_operating_widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].text = ride->GetRideTypeDescriptor() .NameConvention.vehicle == RideComponentType::Boat ? STR_LEAVE_IF_ANOTHER_BOAT_ARRIVES : STR_LEAVE_IF_ANOTHER_TRAIN_ARRIVES; } else { window_ride_operating_widgets[WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX].type = WindowWidgetType::Empty; } // Synchronise with adjacent stations if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_CAN_SYNCHRONISE_ADJACENT_STATIONS)) { window_ride_operating_widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].type = WindowWidgetType::Checkbox; window_ride_operating_widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].image = STR_SYNCHRONISE_WITH_ADJACENT_STATIONS; window_ride_operating_widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].tooltip = STR_SYNCHRONISE_WITH_ADJACENT_STATIONS_TIP; } else { window_ride_operating_widgets[WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX].type = WindowWidgetType::Empty; } // Mode window_ride_operating_widgets[WIDX_MODE].text = RideModeNames[static_cast(ride->mode)]; // Waiting window_ride_operating_widgets[WIDX_LOAD].text = VehicleLoadNames[(ride->depart_flags & RIDE_DEPART_WAIT_FOR_LOAD_MASK)]; if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_LOAD_OPTIONS)) { window_ride_operating_widgets[WIDX_LOAD_CHECKBOX].type = WindowWidgetType::Checkbox; window_ride_operating_widgets[WIDX_LOAD].type = WindowWidgetType::DropdownMenu; window_ride_operating_widgets[WIDX_LOAD_DROPDOWN].type = WindowWidgetType::Button; window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Checkbox; window_ride_operating_widgets[WIDX_MINIMUM_LENGTH].type = WindowWidgetType::Spinner; window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_INCREASE].type = WindowWidgetType::Button; window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_DECREASE].type = WindowWidgetType::Button; window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Checkbox; window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH].type = WindowWidgetType::Spinner; window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_INCREASE].type = WindowWidgetType::Button; window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_DECREASE].type = WindowWidgetType::Button; ft.Rewind(); ft.Increment(10); ft.Add(STR_FORMAT_SECONDS); ft.Add(ride->min_waiting_time); ft.Add(STR_FORMAT_SECONDS); ft.Add(ride->max_waiting_time); if (ride->depart_flags & RIDE_DEPART_WAIT_FOR_LOAD) w->pressed_widgets |= (1ULL << WIDX_LOAD_CHECKBOX); } else { window_ride_operating_widgets[WIDX_LOAD_CHECKBOX].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_LOAD].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_LOAD_DROPDOWN].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_MINIMUM_LENGTH].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_INCREASE].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_MINIMUM_LENGTH_DECREASE].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_CHECKBOX].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_INCREASE].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_MAXIMUM_LENGTH_DECREASE].type = WindowWidgetType::Empty; } if (ride->depart_flags & RIDE_DEPART_LEAVE_WHEN_ANOTHER_ARRIVES) w->pressed_widgets |= (1ULL << WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX); if (ride->depart_flags & RIDE_DEPART_SYNCHRONISE_WITH_ADJACENT_STATIONS) w->pressed_widgets |= (1ULL << WIDX_SYNCHRONISE_WITH_ADJACENT_STATIONS_CHECKBOX); if (ride->depart_flags & RIDE_DEPART_WAIT_FOR_MINIMUM_LENGTH) w->pressed_widgets |= (1ULL << WIDX_MINIMUM_LENGTH_CHECKBOX); if (ride->depart_flags & RIDE_DEPART_WAIT_FOR_MAXIMUM_LENGTH) w->pressed_widgets |= (1ULL << WIDX_MAXIMUM_LENGTH_CHECKBOX); // Mode specific functionality auto multiplier = ride->GetRideTypeDescriptor().OperatingSettings.OperatingSettingMultiplier; ft.Rewind(); ft.Increment(18); ft.Add(static_cast(ride->operation_option) * multiplier); switch (ride->mode) { case RideMode::PoweredLaunchPasstrough: case RideMode::PoweredLaunch: case RideMode::UpwardLaunch: case RideMode::PoweredLaunchBlockSectioned: ft.Rewind(); ft.Increment(18); ft.Add((ride->launch_speed * 9) / 4); format = STR_RIDE_MODE_SPEED_VALUE; caption = STR_LAUNCH_SPEED; tooltip = STR_LAUNCH_SPEED_TIP; break; case RideMode::StationToStation: ft.Rewind(); ft.Increment(18); ft.Add((ride->speed * 9) / 4); format = STR_RIDE_MODE_SPEED_VALUE; caption = STR_SPEED; tooltip = STR_SPEED_TIP; break; case RideMode::Race: ft.Rewind(); ft.Increment(18); ft.Add(ride->NumLaps); format = STR_NUMBER_OF_LAPS_VALUE; caption = STR_NUMBER_OF_LAPS; tooltip = STR_NUMBER_OF_LAPS_TIP; break; case RideMode::Dodgems: format = STR_RIDE_MODE_TIME_LIMIT_VALUE; caption = STR_TIME_LIMIT; tooltip = STR_TIME_LIMIT_TIP; break; case RideMode::Swing: format = STR_RIDE_MODE_NUMBER_OF_SWINGS_VALUE; caption = STR_NUMBER_OF_SWINGS; tooltip = STR_NUMBER_OF_SWINGS_TIP; break; case RideMode::Rotation: case RideMode::ForwardRotation: case RideMode::BackwardRotation: format = STR_NUMBER_OF_ROTATIONS_VALUE; caption = STR_NUMBER_OF_ROTATIONS; tooltip = STR_NUMBER_OF_ROTATIONS_TIP; break; default: format = STR_MAX_PEOPLE_ON_RIDE_VALUE; caption = STR_MAX_PEOPLE_ON_RIDE; tooltip = STR_MAX_PEOPLE_ON_RIDE_TIP; if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES)) format = 0; break; } if (format != 0) { window_ride_operating_widgets[WIDX_MODE_TWEAK_LABEL].type = WindowWidgetType::Label; window_ride_operating_widgets[WIDX_MODE_TWEAK_LABEL].text = caption; window_ride_operating_widgets[WIDX_MODE_TWEAK_LABEL].tooltip = tooltip; window_ride_operating_widgets[WIDX_MODE_TWEAK].type = WindowWidgetType::Spinner; window_ride_operating_widgets[WIDX_MODE_TWEAK].text = format; window_ride_operating_widgets[WIDX_MODE_TWEAK_INCREASE].type = WindowWidgetType::Button; window_ride_operating_widgets[WIDX_MODE_TWEAK_DECREASE].type = WindowWidgetType::Button; w->pressed_widgets &= ~(1ULL << WIDX_LEAVE_WHEN_ANOTHER_ARRIVES_CHECKBOX); } else { window_ride_operating_widgets[WIDX_MODE_TWEAK_LABEL].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_MODE_TWEAK].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_MODE_TWEAK_INCREASE].type = WindowWidgetType::Empty; window_ride_operating_widgets[WIDX_MODE_TWEAK_DECREASE].type = WindowWidgetType::Empty; } WindowRideAnchorBorderWidgets(w); window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10); } /** * * rct2: 0x006B1001 */ static void WindowRideOperatingPaint(rct_window* w, rct_drawpixelinfo* dpi) { WindowDrawWidgets(*w, dpi); WindowRideDrawTabImages(dpi, w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; // Horizontal rule between mode settings and depart settings gfx_fill_rect_inset( dpi, { w->windowPos + ScreenCoordsXY{ window_ride_operating_widgets[WIDX_PAGE_BACKGROUND].left + 4, 103 }, w->windowPos + ScreenCoordsXY{ window_ride_operating_widgets[WIDX_PAGE_BACKGROUND].right - 5, 104 } }, w->colours[1], INSET_RECT_FLAG_BORDER_INSET); // Number of block sections if (ride->IsBlockSectioned()) { auto ft = Formatter(); ft.Add(ride->num_block_brakes + ride->num_stations); DrawTextBasic( dpi, w->windowPos + ScreenCoordsXY{ 21, ride->mode == RideMode::PoweredLaunchBlockSectioned ? 89 : 61 }, STR_BLOCK_SECTIONS, ft, COLOUR_BLACK); } } #pragma endregion #pragma region Maintenance /** * * rct2: 0x006B1AE4 */ static void WindowRideLocateMechanic(rct_window* w) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; // First check if there is a mechanic assigned Peep* mechanic = ride_get_assigned_mechanic(ride); // Otherwise find the closest mechanic if (mechanic == nullptr) mechanic = ride_find_closest_mechanic(ride, 1); if (mechanic == nullptr) context_show_error(STR_UNABLE_TO_LOCATE_MECHANIC, STR_NONE, {}); else { auto intent = Intent(WindowClass::Peep); intent.putExtra(INTENT_EXTRA_PEEP, mechanic); context_open_intent(&intent); } } /** * * rct2: 0x006B7D08 */ static void WindowRideMaintenanceDrawBar( rct_window* w, rct_drawpixelinfo* dpi, const ScreenCoordsXY& coords, int32_t value, int32_t colour) { gfx_fill_rect_inset(dpi, { coords, coords + ScreenCoordsXY{ 149, 8 } }, w->colours[1], INSET_RECT_F_30); if (colour & BAR_BLINK) { colour &= ~BAR_BLINK; if (game_is_not_paused() && (gCurrentRealTimeTicks & 8)) return; } value = ((186 * ((value * 2) & 0xFF)) >> 8) & 0xFF; if (value > 2) { gfx_fill_rect_inset(dpi, { coords + ScreenCoordsXY{ 2, 1 }, coords + ScreenCoordsXY{ value + 1, 7 } }, colour, 0); } } /** * * rct2: 0x006B1AAD */ static void WindowRideMaintenanceMouseup(rct_window* w, WidgetIndex widgetIndex) { switch (widgetIndex) { case WIDX_CLOSE: window_close(*w); break; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: case WIDX_TAB_5: case WIDX_TAB_6: case WIDX_TAB_7: case WIDX_TAB_8: case WIDX_TAB_9: case WIDX_TAB_10: WindowRideSetPage(w, widgetIndex - WIDX_TAB_1); break; case WIDX_LOCATE_MECHANIC: WindowRideLocateMechanic(w); break; case WIDX_REFURBISH_RIDE: context_open_detail_window(WD_REFURBISH_RIDE, w->number); break; } } /** * * rct2: 0x006B1D70 */ static void WindowRideMaintenanceResize(rct_window* w) { window_set_resize(*w, 316, 135, 316, 135); } /** * * rct2: 0x006B1ACE */ static void WindowRideMaintenanceMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr) return; rct_widget* dropdownWidget = widget; int32_t j, num_items; switch (widgetIndex) { case WIDX_INSPECTION_INTERVAL_DROPDOWN: dropdownWidget--; for (int32_t i = 0; i < 7; i++) { gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[i].Args = RideInspectionIntervalNames[i]; } WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, Dropdown::Flag::StayOpen, 7, widget->right - dropdownWidget->left); Dropdown::SetChecked(ride->inspection_interval, true); break; case WIDX_FORCE_BREAKDOWN: num_items = 1; for (j = 0; j < RCT2::ObjectLimits::MaxRideTypesPerRideEntry; j++) { if (rideEntry->ride_type[j] != RIDE_TYPE_NULL) break; } gDropdownItems[0].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[0].Args = STR_DEBUG_FIX_RIDE; for (int32_t i = 0; i < 8; i++) { assert(j < static_cast(std::size(rideEntry->ride_type))); if (GetRideTypeDescriptor(rideEntry->ride_type[j]).AvailableBreakdowns & static_cast(1 << i)) { if (i == BREAKDOWN_BRAKES_FAILURE && ride->IsBlockSectioned()) { if (ride->NumTrains != 1) continue; } gDropdownItems[num_items].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[num_items].Args = RideBreakdownReasonNames[i]; num_items++; } } if (num_items == 1) { context_show_error(STR_DEBUG_NO_BREAKDOWNS_AVAILABLE, STR_NONE, {}); } else { WindowDropdownShowText( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], Dropdown::Flag::StayOpen, num_items); num_items = 1; int32_t breakdownReason = ride->breakdown_reason_pending; if (breakdownReason != BREAKDOWN_NONE && (ride->lifecycle_flags & RIDE_LIFECYCLE_BREAKDOWN_PENDING)) { for (int32_t i = 0; i < 8; i++) { if (GetRideTypeDescriptor(rideEntry->ride_type[j]).AvailableBreakdowns & static_cast(1 << i)) { if (i == BREAKDOWN_BRAKES_FAILURE && ride->IsBlockSectioned()) { if (ride->NumTrains != 1) continue; } if (i == breakdownReason) { Dropdown::SetChecked(num_items, true); break; } gDropdownItems[num_items].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[num_items].Args = RideBreakdownReasonNames[i]; num_items++; } } } if ((ride->lifecycle_flags & RIDE_LIFECYCLE_BREAKDOWN_PENDING) == 0) { Dropdown::SetDisabled(0, true); } } break; } } /** * * rct2: 0x006B1AD9 */ static void WindowRideMaintenanceDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex) { if (dropdownIndex == -1) return; auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr) return; switch (widgetIndex) { case WIDX_INSPECTION_INTERVAL_DROPDOWN: set_operating_setting(w->rideId, RideSetSetting::InspectionInterval, dropdownIndex); break; case WIDX_FORCE_BREAKDOWN: if (dropdownIndex == 0) { Vehicle* vehicle; switch (ride->breakdown_reason_pending) { case BREAKDOWN_SAFETY_CUT_OUT: if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_TRACK)) break; for (int32_t i = 0; i < ride->NumTrains; ++i) { for (vehicle = GetEntity(ride->vehicles[i]); vehicle != nullptr; vehicle = GetEntity(vehicle->next_vehicle_on_train)) { vehicle->ClearUpdateFlag( VEHICLE_UPDATE_FLAG_BROKEN_CAR | VEHICLE_UPDATE_FLAG_ZERO_VELOCITY | VEHICLE_UPDATE_FLAG_BROKEN_TRAIN); } } break; case BREAKDOWN_RESTRAINTS_STUCK_CLOSED: case BREAKDOWN_RESTRAINTS_STUCK_OPEN: case BREAKDOWN_DOORS_STUCK_CLOSED: case BREAKDOWN_DOORS_STUCK_OPEN: vehicle = GetEntity(ride->vehicles[ride->broken_vehicle]); if (vehicle != nullptr) { vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_CAR); } break; case BREAKDOWN_VEHICLE_MALFUNCTION: vehicle = GetEntity(ride->vehicles[ride->broken_vehicle]); if (vehicle != nullptr) { vehicle->ClearUpdateFlag(VEHICLE_UPDATE_FLAG_BROKEN_TRAIN); } break; } ride->lifecycle_flags &= ~(RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN); window_invalidate_by_number(WindowClass::Ride, w->number); break; } if (ride->lifecycle_flags & (RIDE_LIFECYCLE_BREAKDOWN_PENDING | RIDE_LIFECYCLE_BROKEN_DOWN | RIDE_LIFECYCLE_CRASHED)) { context_show_error(STR_DEBUG_CANT_FORCE_BREAKDOWN, STR_DEBUG_RIDE_ALREADY_BROKEN, {}); } else if (ride->status == RideStatus::Closed) { context_show_error(STR_DEBUG_CANT_FORCE_BREAKDOWN, STR_DEBUG_RIDE_IS_CLOSED, {}); } else { int32_t j; for (j = 0; j < RCT2::ObjectLimits::MaxRideTypesPerRideEntry; j++) { if (rideEntry->ride_type[j] != RIDE_TYPE_NULL) break; } int32_t i; int32_t num_items = 1; for (i = 0; i < BREAKDOWN_COUNT; i++) { assert(j < static_cast(std::size(rideEntry->ride_type))); if (GetRideTypeDescriptor(rideEntry->ride_type[j]).AvailableBreakdowns & static_cast(1 << i)) { if (i == BREAKDOWN_BRAKES_FAILURE && ride->IsBlockSectioned()) { if (ride->NumTrains != 1) continue; } if (num_items == dropdownIndex) break; num_items++; } } ride_prepare_breakdown(ride, i); } break; } } /** * * rct2: 0x006B1D37 */ static void WindowRideMaintenanceUpdate(rct_window* w) { w->frame_no++; window_event_invalidate_call(w); widget_invalidate(*w, WIDX_TAB_4); auto ride = get_ride(w->rideId); if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_MAINTENANCE) { ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_MAINTENANCE; w->Invalidate(); } } /** * * rct2: 0x006B17C8 */ static void WindowRideMaintenanceInvalidate(rct_window* w) { auto widgets = window_ride_page_widgets[w->page]; if (w->widgets != widgets) { w->widgets = widgets; WindowInitScrollWidgets(*w); } WindowRideSetPressedTab(w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto ft = Formatter::Common(); ride->FormatNameTo(ft); window_ride_maintenance_widgets[WIDX_INSPECTION_INTERVAL].text = RideInspectionIntervalNames[ride->inspection_interval]; WindowRideAnchorBorderWidgets(w); window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10); if (gConfigGeneral.debugging_tools && network_get_mode() == NETWORK_MODE_NONE) { window_ride_maintenance_widgets[WIDX_FORCE_BREAKDOWN].type = WindowWidgetType::FlatBtn; } else { window_ride_maintenance_widgets[WIDX_FORCE_BREAKDOWN].type = WindowWidgetType::Empty; } if (ride->GetRideTypeDescriptor().AvailableBreakdowns == 0 || !(ride->lifecycle_flags & RIDE_LIFECYCLE_EVER_BEEN_OPENED)) { w->disabled_widgets |= (1ULL << WIDX_REFURBISH_RIDE); window_ride_maintenance_widgets[WIDX_REFURBISH_RIDE].tooltip = STR_CANT_REFURBISH_NOT_NEEDED; } else { w->disabled_widgets &= ~(1ULL << WIDX_REFURBISH_RIDE); window_ride_maintenance_widgets[WIDX_REFURBISH_RIDE].tooltip = STR_REFURBISH_RIDE_TIP; } } /** * * rct2: 0x006B1877 */ static void WindowRideMaintenancePaint(rct_window* w, rct_drawpixelinfo* dpi) { WindowDrawWidgets(*w, dpi); WindowRideDrawTabImages(dpi, w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; // Locate mechanic button image rct_widget* widget = &window_ride_maintenance_widgets[WIDX_LOCATE_MECHANIC]; auto screenCoords = w->windowPos + ScreenCoordsXY{ widget->left, widget->top }; auto image = ImageId(SPR_MECHANIC, COLOUR_BLACK, gStaffMechanicColour); gfx_draw_sprite(dpi, image, screenCoords); // Inspection label widget = &window_ride_maintenance_widgets[WIDX_INSPECTION_INTERVAL]; screenCoords = w->windowPos + ScreenCoordsXY{ 4, widget->top + 1 }; DrawTextBasic(dpi, screenCoords, STR_INSPECTION); // Reliability widget = &window_ride_maintenance_widgets[WIDX_PAGE_BACKGROUND]; screenCoords = w->windowPos + ScreenCoordsXY{ widget->left + 4, widget->top + 4 }; uint16_t reliability = ride->reliability_percentage; auto ft = Formatter(); ft.Add(reliability); DrawTextBasic(dpi, screenCoords, STR_RELIABILITY_LABEL_1757, ft); WindowRideMaintenanceDrawBar( w, dpi, screenCoords + ScreenCoordsXY{ 103, 0 }, std::max(10, reliability), COLOUR_BRIGHT_GREEN); screenCoords.y += 11; uint16_t downTime = ride->downtime; ft = Formatter(); ft.Add(downTime); DrawTextBasic(dpi, screenCoords, STR_DOWN_TIME_LABEL_1889, ft); WindowRideMaintenanceDrawBar(w, dpi, screenCoords + ScreenCoordsXY{ 103, 0 }, downTime, COLOUR_BRIGHT_RED); screenCoords.y += 26; // Last inspection StringId stringId; if (ride->last_inspection <= 1) stringId = STR_TIME_SINCE_LAST_INSPECTION_MINUTE; else if (ride->last_inspection <= 240) stringId = STR_TIME_SINCE_LAST_INSPECTION_MINUTES; else stringId = STR_TIME_SINCE_LAST_INSPECTION_MORE_THAN_4_HOURS; ft = Formatter(); ft.Add(ride->last_inspection); DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += 12; // Last / current breakdown if (ride->breakdown_reason == BREAKDOWN_NONE) return; stringId = (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) ? STR_CURRENT_BREAKDOWN : STR_LAST_BREAKDOWN; ft = Formatter(); ft.Add(RideBreakdownReasonNames[ride->breakdown_reason]); DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += 12; // Mechanic status if (ride->lifecycle_flags & RIDE_LIFECYCLE_BROKEN_DOWN) { switch (ride->mechanic_status) { case RIDE_MECHANIC_STATUS_CALLING: { stringId = STR_NO_MECHANICS_ARE_HIRED_MESSAGE; for (auto peep : EntityList()) { if (peep->IsMechanic()) { stringId = STR_CALLING_MECHANIC; break; } } break; } case RIDE_MECHANIC_STATUS_HEADING: stringId = STR_MEHCANIC_IS_HEADING_FOR_THE_RIDE; break; case RIDE_MECHANIC_STATUS_FIXING: case RIDE_MECHANIC_STATUS_HAS_FIXED_STATION_BRAKES: stringId = STR_MEHCANIC_IS_FIXING_THE_RIDE; break; default: stringId = STR_EMPTY; break; } if (stringId != STR_EMPTY) { if (stringId == STR_CALLING_MECHANIC || stringId == STR_NO_MECHANICS_ARE_HIRED_MESSAGE) { DrawTextWrapped(dpi, screenCoords, 280, stringId, {}, { TextAlignment::LEFT }); } else { auto staff = GetEntity(ride->mechanic); if (staff != nullptr && staff->IsMechanic()) { ft = Formatter(); staff->FormatNameTo(ft); DrawTextWrapped(dpi, screenCoords, 280, stringId, ft, { TextAlignment::LEFT }); } } } } } #pragma endregion #pragma region Colour static int32_t WindowRideHasTrackColour(Ride* ride, int32_t trackColour) { // Get station flags (shops don't have them) auto stationObjFlags = 0; if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP_OR_FACILITY)) { auto stationObj = ride->GetStationObject(); if (stationObj != nullptr) { stationObjFlags = stationObj->Flags; } } switch (trackColour) { case 0: return (stationObjFlags & STATION_OBJECT_FLAGS::HAS_PRIMARY_COLOUR) || ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_MAIN); case 1: return (stationObjFlags & STATION_OBJECT_FLAGS::HAS_SECONDARY_COLOUR) || ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_ADDITIONAL); case 2: return ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_SUPPORTS); default: return 0; } } static void WindowRideSetTrackColourScheme(rct_window* w, const ScreenCoordsXY& screenPos) { auto newColourScheme = static_cast(w->ride_colour); auto info = get_map_coordinates_from_pos(screenPos, EnumsToFlags(ViewportInteractionItem::Ride)); if (info.SpriteType != ViewportInteractionItem::Ride) return; if (info.Element->GetType() != TileElementType::Track) return; if (info.Element->AsTrack()->GetRideIndex() != w->rideId) return; if (info.Element->AsTrack()->GetColourScheme() == newColourScheme) return; auto z = info.Element->GetBaseZ(); auto direction = info.Element->GetDirection(); auto gameAction = RideSetColourSchemeAction( CoordsXYZD{ info.Loc, z, static_cast(direction) }, info.Element->AsTrack()->GetTrackType(), newColourScheme); GameActions::Execute(&gameAction); } /** * * rct2: 0x006B04FA */ static void WindowRideColourClose(rct_window* w) { if (!(input_test_flag(INPUT_FLAG_TOOL_ACTIVE))) return; if (gCurrentToolWidget.window_classification != w->classification) return; if (gCurrentToolWidget.window_number != w->number) return; tool_cancel(); } /** * * rct2: 0x006B02A1 */ static void WindowRideColourMouseup(rct_window* w, WidgetIndex widgetIndex) { switch (widgetIndex) { case WIDX_CLOSE: window_close(*w); break; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: case WIDX_TAB_5: case WIDX_TAB_6: case WIDX_TAB_7: case WIDX_TAB_8: case WIDX_TAB_9: case WIDX_TAB_10: WindowRideSetPage(w, widgetIndex - WIDX_TAB_1); break; case WIDX_PAINT_INDIVIDUAL_AREA: tool_set(*w, WIDX_PAINT_INDIVIDUAL_AREA, Tool::PaintDown); break; case WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX: auto ride = get_ride(w->rideId); if (ride != nullptr) { const bool currentlyEnabled = ride->HasLifecycleFlag(RIDE_LIFECYCLE_RANDOM_SHOP_COLOURS); auto rideSetAppearanceAction = RideSetAppearanceAction( w->rideId, RideSetAppearanceType::SellingItemColourIsRandom, currentlyEnabled ? 0 : 1, 0); GameActions::Execute(&rideSetAppearanceAction); } break; } } /** * * rct2: 0x006B0AB6 */ static void WindowRideColourResize(rct_window* w) { window_set_resize(*w, 316, 207, 316, 207); } /** * * rct2: 0x006B02C6 */ static void WindowRideColourMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget) { VehicleColour vehicleColour; int32_t i, numItems; StringId stringId; auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr) return; auto colourSchemeIndex = w->ride_colour; auto dropdownWidget = widget - 1; switch (widgetIndex) { case WIDX_TRACK_COLOUR_SCHEME_DROPDOWN: for (i = 0; i < OpenRCT2::Limits::NumColourSchemes; i++) { gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[i].Args = ColourSchemeNames[i]; } WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, Dropdown::Flag::StayOpen, 4, widget->right - dropdownWidget->left); Dropdown::SetChecked(colourSchemeIndex, true); break; case WIDX_TRACK_MAIN_COLOUR: WindowDropdownShowColour(w, widget, w->colours[1], ride->track_colour[colourSchemeIndex].main); break; case WIDX_TRACK_ADDITIONAL_COLOUR: WindowDropdownShowColour(w, widget, w->colours[1], ride->track_colour[colourSchemeIndex].additional); break; case WIDX_TRACK_SUPPORT_COLOUR: WindowDropdownShowColour(w, widget, w->colours[1], ride->track_colour[colourSchemeIndex].supports); break; case WIDX_MAZE_STYLE_DROPDOWN: for (i = 0; i < 4; i++) { gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[i].Args = MazeOptions[i].text; } WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, Dropdown::Flag::StayOpen, 4, widget->right - dropdownWidget->left); Dropdown::SetChecked(ride->track_colour[colourSchemeIndex].supports, true); break; case WIDX_ENTRANCE_STYLE_DROPDOWN: { auto ddIndex = 0; auto& objManager = GetContext()->GetObjectManager(); for (i = 0; i < MAX_STATION_OBJECTS; i++) { auto stationObj = static_cast(objManager.GetLoadedObject(ObjectType::Station, i)); if (stationObj != nullptr) { gDropdownItems[ddIndex].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[ddIndex].Args = stationObj->NameStringId; if (ride->entrance_style == i) { gDropdownItems[ddIndex].Format = STR_DROPDOWN_MENU_LABEL_SELECTED; } ddIndex++; } } WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, Dropdown::Flag::StayOpen, ddIndex, widget->right - dropdownWidget->left); break; } case WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN: for (i = 0; i < 3; i++) { gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[i].Args = (GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).singular << 16) | VehicleColourSchemeNames[i]; } WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, Dropdown::Flag::StayOpen, rideEntry->max_cars_in_train > 1 ? 3 : 2, widget->right - dropdownWidget->left); Dropdown::SetChecked(ride->colour_scheme_type & 3, true); break; case WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN: numItems = ride->NumTrains; if ((ride->colour_scheme_type & 3) != VEHICLE_COLOUR_SCHEME_PER_TRAIN) numItems = ride->num_cars_per_train; stringId = (ride->colour_scheme_type & 3) == VEHICLE_COLOUR_SCHEME_PER_TRAIN ? STR_RIDE_COLOUR_TRAIN_OPTION : STR_RIDE_COLOUR_VEHICLE_OPTION; for (i = 0; i < std::min(numItems, Dropdown::ItemsMaxSize); i++) { gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[i].Args = (static_cast(i + 1) << 32) | ((GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).capitalised) << 16) | stringId; } WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, Dropdown::Flag::StayOpen, numItems, widget->right - dropdownWidget->left); Dropdown::SetChecked(w->vehicleIndex, true); break; case WIDX_VEHICLE_BODY_COLOR: vehicleColour = ride_get_vehicle_colour(ride, w->vehicleIndex); WindowDropdownShowColour(w, widget, w->colours[1], vehicleColour.Body); break; case WIDX_VEHICLE_TRIM_COLOUR: vehicleColour = ride_get_vehicle_colour(ride, w->vehicleIndex); WindowDropdownShowColour(w, widget, w->colours[1], vehicleColour.Trim); break; case WIDX_VEHICLE_TERNARY_COLOUR: vehicleColour = ride_get_vehicle_colour(ride, w->vehicleIndex); WindowDropdownShowColour(w, widget, w->colours[1], vehicleColour.Tertiary); break; } } /** * * rct2: 0x006B0331 */ static void WindowRideColourDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex) { if (dropdownIndex == -1) return; const auto rideId = w->rideId; switch (widgetIndex) { case WIDX_TRACK_COLOUR_SCHEME_DROPDOWN: w->ride_colour = static_cast(dropdownIndex); w->Invalidate(); break; case WIDX_TRACK_MAIN_COLOUR: { auto rideSetAppearanceAction = RideSetAppearanceAction( rideId, RideSetAppearanceType::TrackColourMain, dropdownIndex, w->ride_colour); GameActions::Execute(&rideSetAppearanceAction); } break; case WIDX_TRACK_ADDITIONAL_COLOUR: { auto rideSetAppearanceAction = RideSetAppearanceAction( rideId, RideSetAppearanceType::TrackColourAdditional, dropdownIndex, w->ride_colour); GameActions::Execute(&rideSetAppearanceAction); } break; case WIDX_TRACK_SUPPORT_COLOUR: { auto rideSetAppearanceAction = RideSetAppearanceAction( rideId, RideSetAppearanceType::TrackColourSupports, dropdownIndex, w->ride_colour); GameActions::Execute(&rideSetAppearanceAction); } break; case WIDX_MAZE_STYLE_DROPDOWN: { auto rideSetAppearanceAction = RideSetAppearanceAction( rideId, RideSetAppearanceType::MazeStyle, dropdownIndex, w->ride_colour); GameActions::Execute(&rideSetAppearanceAction); } break; case WIDX_ENTRANCE_STYLE_DROPDOWN: { auto ddIndex = 0; auto& objManager = GetContext()->GetObjectManager(); for (auto i = 0; i < MAX_STATION_OBJECTS; i++) { auto stationObj = static_cast(objManager.GetLoadedObject(ObjectType::Station, i)); if (stationObj != nullptr) { if (ddIndex == dropdownIndex) { auto rideSetAppearanceAction = RideSetAppearanceAction( rideId, RideSetAppearanceType::EntranceStyle, ddIndex, 0); rideSetAppearanceAction.SetCallback([ddIndex](const GameAction*, const GameActions::Result* res) { if (res->Error != GameActions::Status::Ok) return; gLastEntranceStyle = ddIndex; }); GameActions::Execute(&rideSetAppearanceAction); break; } ddIndex++; } } break; } case WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN: { auto rideSetAppearanceAction = RideSetAppearanceAction( rideId, RideSetAppearanceType::VehicleColourScheme, dropdownIndex, 0); GameActions::Execute(&rideSetAppearanceAction); w->vehicleIndex = 0; } break; case WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN: w->vehicleIndex = dropdownIndex; w->Invalidate(); break; case WIDX_VEHICLE_BODY_COLOR: { auto rideSetAppearanceAction = RideSetAppearanceAction( rideId, RideSetAppearanceType::VehicleColourBody, dropdownIndex, w->vehicleIndex); GameActions::Execute(&rideSetAppearanceAction); } break; case WIDX_VEHICLE_TRIM_COLOUR: { auto rideSetAppearanceAction = RideSetAppearanceAction( rideId, RideSetAppearanceType::VehicleColourTrim, dropdownIndex, w->vehicleIndex); GameActions::Execute(&rideSetAppearanceAction); } break; case WIDX_VEHICLE_TERNARY_COLOUR: { auto rideSetAppearanceAction = RideSetAppearanceAction( rideId, RideSetAppearanceType::VehicleColourTernary, dropdownIndex, w->vehicleIndex); GameActions::Execute(&rideSetAppearanceAction); } break; } } /** * * rct2: 0x006B0A8F */ static void WindowRideColourUpdate(rct_window* w) { w->frame_no++; window_event_invalidate_call(w); widget_invalidate(*w, WIDX_TAB_5); widget_invalidate(*w, WIDX_VEHICLE_PREVIEW); } /** * * rct2: 0x006B04EC */ static void WindowRideColourTooldown(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) { if (widgetIndex == WIDX_PAINT_INDIVIDUAL_AREA) WindowRideSetTrackColourScheme(w, screenCoords); } /** * * rct2: 0x006B04F3 */ static void WindowRideColourTooldrag(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) { if (widgetIndex == WIDX_PAINT_INDIVIDUAL_AREA) WindowRideSetTrackColourScheme(w, screenCoords); } /** * * rct2: 0x006AFB36 */ static void WindowRideColourInvalidate(rct_window* w) { TrackColour trackColour; VehicleColour vehicleColour; auto widgets = window_ride_page_widgets[w->page]; if (w->widgets != widgets) { w->widgets = widgets; WindowInitScrollWidgets(*w); } WindowRideSetPressedTab(w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr) return; w->widgets[WIDX_TITLE].text = STR_ARG_16_STRINGID; auto ft = Formatter::Common(); ft.Increment(16); ride->FormatNameTo(ft); // Track colours int32_t colourScheme = w->ride_colour; trackColour = ride_get_track_colour(ride, colourScheme); // Maze style if (ride->type == RIDE_TYPE_MAZE) { window_ride_colour_widgets[WIDX_MAZE_STYLE].type = WindowWidgetType::DropdownMenu; window_ride_colour_widgets[WIDX_MAZE_STYLE_DROPDOWN].type = WindowWidgetType::Button; window_ride_colour_widgets[WIDX_MAZE_STYLE].text = MazeOptions[trackColour.supports].text; } else { window_ride_colour_widgets[WIDX_MAZE_STYLE].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_MAZE_STYLE_DROPDOWN].type = WindowWidgetType::Empty; } // Track, multiple colour schemes if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_SUPPORTS_MULTIPLE_TRACK_COLOUR)) { window_ride_colour_widgets[WIDX_TRACK_COLOUR_SCHEME].type = WindowWidgetType::DropdownMenu; window_ride_colour_widgets[WIDX_TRACK_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Button; window_ride_colour_widgets[WIDX_PAINT_INDIVIDUAL_AREA].type = WindowWidgetType::FlatBtn; } else { window_ride_colour_widgets[WIDX_TRACK_COLOUR_SCHEME].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_TRACK_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_PAINT_INDIVIDUAL_AREA].type = WindowWidgetType::Empty; } // Track main colour if (WindowRideHasTrackColour(ride, 0)) { window_ride_colour_widgets[WIDX_TRACK_MAIN_COLOUR].type = WindowWidgetType::ColourBtn; window_ride_colour_widgets[WIDX_TRACK_MAIN_COLOUR].image = GetColourButtonImage(trackColour.main).ToUInt32(); } else { window_ride_colour_widgets[WIDX_TRACK_MAIN_COLOUR].type = WindowWidgetType::Empty; } // Track additional colour if (WindowRideHasTrackColour(ride, 1)) { window_ride_colour_widgets[WIDX_TRACK_ADDITIONAL_COLOUR].type = WindowWidgetType::ColourBtn; window_ride_colour_widgets[WIDX_TRACK_ADDITIONAL_COLOUR].image = GetColourButtonImage(trackColour.additional) .ToUInt32(); } else { window_ride_colour_widgets[WIDX_TRACK_ADDITIONAL_COLOUR].type = WindowWidgetType::Empty; } // Selling item random colour checkbox if (ride->HasRecolourableShopItems()) { window_ride_colour_widgets[WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX].type = WindowWidgetType::Checkbox; if (ride->HasLifecycleFlag(RIDE_LIFECYCLE_RANDOM_SHOP_COLOURS)) { w->pressed_widgets |= (1ULL << WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX); } else { w->pressed_widgets &= ~(1ULL << WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX); } } else { window_ride_colour_widgets[WIDX_SELL_ITEM_RANDOM_COLOUR_CHECKBOX].type = WindowWidgetType::Empty; } // Track supports colour if (WindowRideHasTrackColour(ride, 2) && ride->type != RIDE_TYPE_MAZE) { window_ride_colour_widgets[WIDX_TRACK_SUPPORT_COLOUR].type = WindowWidgetType::ColourBtn; window_ride_colour_widgets[WIDX_TRACK_SUPPORT_COLOUR].image = GetColourButtonImage(trackColour.supports).ToUInt32(); } else { window_ride_colour_widgets[WIDX_TRACK_SUPPORT_COLOUR].type = WindowWidgetType::Empty; } // Track preview if (ride->GetRideTypeDescriptor().HasFlag( RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_MAIN | RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_ADDITIONAL | RIDE_TYPE_FLAG_HAS_TRACK_COLOUR_SUPPORTS)) window_ride_colour_widgets[WIDX_TRACK_PREVIEW].type = WindowWidgetType::Spinner; else window_ride_colour_widgets[WIDX_TRACK_PREVIEW].type = WindowWidgetType::Empty; // Entrance style if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_ENTRANCE_EXIT)) { window_ride_colour_widgets[WIDX_ENTRANCE_PREVIEW].type = WindowWidgetType::Spinner; window_ride_colour_widgets[WIDX_ENTRANCE_STYLE].type = WindowWidgetType::DropdownMenu; window_ride_colour_widgets[WIDX_ENTRANCE_STYLE_DROPDOWN].type = WindowWidgetType::Button; auto stringId = STR_NONE; auto stationObj = ride->GetStationObject(); if (stationObj != nullptr) { stringId = stationObj->NameStringId; } window_ride_colour_widgets[WIDX_ENTRANCE_STYLE].text = stringId; } else { window_ride_colour_widgets[WIDX_ENTRANCE_PREVIEW].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_ENTRANCE_STYLE].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_ENTRANCE_STYLE_DROPDOWN].type = WindowWidgetType::Empty; } // Vehicle colours if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_NO_VEHICLES) && ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_VEHICLE_COLOURS)) { int32_t vehicleColourSchemeType = ride->colour_scheme_type & 3; if (vehicleColourSchemeType == 0) w->vehicleIndex = 0; vehicleColour = ride_get_vehicle_colour(ride, w->vehicleIndex); window_ride_colour_widgets[WIDX_VEHICLE_PREVIEW].type = WindowWidgetType::Scroll; window_ride_colour_widgets[WIDX_VEHICLE_BODY_COLOR].type = WindowWidgetType::ColourBtn; window_ride_colour_widgets[WIDX_VEHICLE_BODY_COLOR].image = GetColourButtonImage(vehicleColour.Body).ToUInt32(); bool allowChangingTrimColour = false; bool allowChangingTernaryColour = false; for (int32_t i = 0; i < ride->num_cars_per_train; i++) { uint8_t vehicleTypeIndex = ride_entry_get_vehicle_at_position(ride->subtype, ride->num_cars_per_train, i); if (rideEntry->Cars[vehicleTypeIndex].flags & CAR_ENTRY_FLAG_ENABLE_TRIM_COLOUR) { allowChangingTrimColour = true; } if (rideEntry->Cars[vehicleTypeIndex].flags & CAR_ENTRY_FLAG_ENABLE_TERNARY_COLOUR) { allowChangingTernaryColour = true; } } // Additional colours if (allowChangingTrimColour) { window_ride_colour_widgets[WIDX_VEHICLE_TRIM_COLOUR].type = WindowWidgetType::ColourBtn; window_ride_colour_widgets[WIDX_VEHICLE_TRIM_COLOUR].image = GetColourButtonImage(vehicleColour.Trim).ToUInt32(); if (allowChangingTernaryColour) { window_ride_colour_widgets[WIDX_VEHICLE_TERNARY_COLOUR].type = WindowWidgetType::ColourBtn; window_ride_colour_widgets[WIDX_VEHICLE_TERNARY_COLOUR].image = GetColourButtonImage(vehicleColour.Tertiary) .ToUInt32(); } else { window_ride_colour_widgets[WIDX_VEHICLE_TERNARY_COLOUR].type = WindowWidgetType::Empty; } } else { window_ride_colour_widgets[WIDX_VEHICLE_TRIM_COLOUR].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_VEHICLE_TERNARY_COLOUR].type = WindowWidgetType::Empty; } // Vehicle colour scheme type if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_VEHICLE_IS_INTEGRAL) && (ride->num_cars_per_train | ride->NumTrains) > 1) { window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME].type = WindowWidgetType::DropdownMenu; window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Button; } else { window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Empty; } ft.Rewind(); ft.Increment(6); ft.Add(VehicleColourSchemeNames[vehicleColourSchemeType]); ft.Add(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).singular); ft.Add(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).capitalised); ft.Add(w->vehicleIndex + 1); // Vehicle index if (vehicleColourSchemeType != 0) { window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX].type = WindowWidgetType::DropdownMenu; window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN].type = WindowWidgetType::Button; window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX].text = vehicleColourSchemeType == 1 ? STR_RIDE_COLOUR_TRAIN_VALUE : STR_RIDE_COLOUR_VEHICLE_VALUE; } else { window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN].type = WindowWidgetType::Empty; } } else { window_ride_colour_widgets[WIDX_VEHICLE_PREVIEW].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_SCHEME_DROPDOWN].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_VEHICLE_COLOUR_INDEX_DROPDOWN].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_VEHICLE_BODY_COLOR].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_VEHICLE_TRIM_COLOUR].type = WindowWidgetType::Empty; window_ride_colour_widgets[WIDX_VEHICLE_TERNARY_COLOUR].type = WindowWidgetType::Empty; } ft.Rewind(); ft.Increment(14); ft.Add(ColourSchemeNames[colourScheme]); WindowRideAnchorBorderWidgets(w); window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10); } /** * * rct2: 0x006AFF3E */ static void WindowRideColourPaint(rct_window* w, rct_drawpixelinfo* dpi) { // TODO: This should use lists and identified sprites rct_drawpixelinfo clippedDpi; auto ride = get_ride(w->rideId); if (ride == nullptr) return; WindowDrawWidgets(*w, dpi); WindowRideDrawTabImages(dpi, w); // Track / shop item preview const auto& trackPreviewWidget = window_ride_colour_widgets[WIDX_TRACK_PREVIEW]; if (trackPreviewWidget.type != WindowWidgetType::Empty) gfx_fill_rect( dpi, { { w->windowPos + ScreenCoordsXY{ trackPreviewWidget.left + 1, trackPreviewWidget.top + 1 } }, { w->windowPos + ScreenCoordsXY{ trackPreviewWidget.right - 1, trackPreviewWidget.bottom - 1 } } }, PALETTE_INDEX_12); auto trackColour = ride_get_track_colour(ride, w->ride_colour); // auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr || rideEntry->shop_item[0] == ShopItem::None) { auto screenCoords = w->windowPos + ScreenCoordsXY{ trackPreviewWidget.left, trackPreviewWidget.top }; // Track if (ride->type == RIDE_TYPE_MAZE) { gfx_draw_sprite(dpi, ImageId(MazeOptions[trackColour.supports].sprite), screenCoords); } else { auto typeDescriptor = ride->GetRideTypeDescriptor(); int32_t spriteIndex = typeDescriptor.ColourPreview.Track; if (spriteIndex != 0) { gfx_draw_sprite(dpi, ImageId(spriteIndex, trackColour.main, trackColour.additional), screenCoords); } // Supports spriteIndex = typeDescriptor.ColourPreview.Supports; if (spriteIndex != 0) { gfx_draw_sprite(dpi, ImageId(spriteIndex, trackColour.supports), screenCoords); } } } else { auto screenCoords = w->windowPos + ScreenCoordsXY{ (trackPreviewWidget.left + trackPreviewWidget.right) / 2 - 8, (trackPreviewWidget.bottom + trackPreviewWidget.top) / 2 - 6 }; ShopItem shopItem = rideEntry->shop_item[1] == ShopItem::None ? rideEntry->shop_item[0] : rideEntry->shop_item[1]; if (ride->HasLifecycleFlag(RIDE_LIFECYCLE_RANDOM_SHOP_COLOURS)) { static colour_t spriteColour = COLOUR_BLACK; // Limit update rate of preview to avoid making people dizzy. if ((gCurrentTicks % 64) == 0) { spriteColour++; if (spriteColour >= COLOUR_COUNT) { spriteColour = COLOUR_BLACK; } } gfx_draw_sprite(dpi, ImageId(GetShopItemDescriptor(shopItem).Image, spriteColour), screenCoords); } else { gfx_draw_sprite(dpi, ImageId(GetShopItemDescriptor(shopItem).Image, ride->track_colour[0].main), screenCoords); } } // Entrance preview trackColour = ride_get_track_colour(ride, 0); const auto& entrancePreviewWidget = w->widgets[WIDX_ENTRANCE_PREVIEW]; if (entrancePreviewWidget.type != WindowWidgetType::Empty) { if (clip_drawpixelinfo( &clippedDpi, dpi, w->windowPos + ScreenCoordsXY{ entrancePreviewWidget.left + 1, entrancePreviewWidget.top + 1 }, entrancePreviewWidget.width(), entrancePreviewWidget.height())) { gfx_clear(&clippedDpi, PALETTE_INDEX_12); auto stationObj = ride->GetStationObject(); if (stationObj != nullptr && stationObj->BaseImageId != ImageIndexUndefined) { auto imageId = ImageId(stationObj->BaseImageId, trackColour.main, trackColour.additional); // Back gfx_draw_sprite(&clippedDpi, imageId, { 34, 20 }); // Front gfx_draw_sprite(&clippedDpi, imageId.WithIndexOffset(4), { 34, 20 }); // Glass if (stationObj->Flags & STATION_OBJECT_FLAGS::IS_TRANSPARENT) { auto glassImageId = ImageId(stationObj->BaseImageId + 20).WithTransparency(trackColour.main); gfx_draw_sprite(&clippedDpi, glassImageId, { 34, 20 }); } } } DrawTextEllipsised(dpi, { w->windowPos.x + 3, w->windowPos.y + 103 }, 97, STR_STATION_STYLE, {}); } } /** * * rct2: 0x006B0192 */ static void WindowRideColourScrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex) { auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr) return; auto vehiclePreviewWidget = &window_ride_colour_widgets[WIDX_VEHICLE_PREVIEW]; auto vehicleColour = ride_get_vehicle_colour(ride, w->vehicleIndex); // Background colour gfx_fill_rect(dpi, { { dpi->x, dpi->y }, { dpi->x + dpi->width - 1, dpi->y + dpi->height - 1 } }, PALETTE_INDEX_12); // ? auto screenCoords = ScreenCoordsXY{ vehiclePreviewWidget->width() / 2, vehiclePreviewWidget->height() - 15 }; // ? auto trainCarIndex = (ride->colour_scheme_type & 3) == RIDE_COLOUR_SCHEME_MODE_DIFFERENT_PER_CAR ? w->vehicleIndex : rideEntry->TabCar; CarEntry* carEntry = &rideEntry->Cars[ride_entry_get_vehicle_at_position( ride->subtype, ride->num_cars_per_train, trainCarIndex)]; screenCoords.y += carEntry->tab_height; // Draw the coloured spinning vehicle // w->frame_no represents a SpritePrecision of 64 ImageIndex imageIndex = carEntry->SpriteByYaw(w->frame_no / 2, SpriteGroupType::SlopeFlat); imageIndex &= carEntry->TabRotationMask; imageIndex *= carEntry->base_num_frames; imageIndex += carEntry->base_image_id; auto imageId = ImageId(imageIndex, vehicleColour.Body, vehicleColour.Trim, vehicleColour.Tertiary); gfx_draw_sprite(dpi, imageId, screenCoords); } #pragma endregion #pragma region Music static std::vector window_ride_current_music_style_order; /** * * rct2: 0x006B215D */ static void WindowRideToggleMusic(rct_window* w) { auto ride = get_ride(w->rideId); if (ride != nullptr) { int32_t activateMusic = (ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC) ? 0 : 1; set_operating_setting(w->rideId, RideSetSetting::Music, activateMusic); } } /** * * rct2: 0x006B1ED7 */ static void WindowRideMusicMouseup(rct_window* w, WidgetIndex widgetIndex) { switch (widgetIndex) { case WIDX_CLOSE: window_close(*w); break; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: case WIDX_TAB_5: case WIDX_TAB_6: case WIDX_TAB_7: case WIDX_TAB_8: case WIDX_TAB_9: case WIDX_TAB_10: WindowRideSetPage(w, widgetIndex - WIDX_TAB_1); break; case WIDX_PLAY_MUSIC: WindowRideToggleMusic(w); break; } } /** * * rct2: 0x006AF4A2 */ static void WindowRideMusicResize(rct_window* w) { w->flags |= WF_RESIZABLE; window_set_resize(*w, 316, 81, 316, 81); } static std::string GetMusicString(ObjectEntryIndex musicObjectIndex) { auto& objManager = GetContext()->GetObjectManager(); auto musicObj = static_cast(objManager.GetLoadedObject(ObjectType::Music, musicObjectIndex)); auto name = musicObj->NameStringId; return format_string(name, {}); } /** * * rct2: 0x006B1EFC */ static void WindowRideMusicMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget) { if (widgetIndex != WIDX_MUSIC_DROPDOWN) return; auto dropdownWidget = widget - 1; auto ride = get_ride(w->rideId); if (ride == nullptr) return; // Construct list of available music auto& musicOrder = window_ride_current_music_style_order; musicOrder.clear(); auto& objManager = GetContext()->GetObjectManager(); for (ObjectEntryIndex i = 0; i < MAX_MUSIC_OBJECTS; i++) { auto musicObj = static_cast(objManager.GetLoadedObject(ObjectType::Music, i)); if (musicObj != nullptr) { // Hide custom music if the WAV file does not exist auto originalStyleId = musicObj->GetOriginalStyleId(); if (originalStyleId.has_value() && (originalStyleId == MUSIC_STYLE_CUSTOM_MUSIC_1 || originalStyleId == MUSIC_STYLE_CUSTOM_MUSIC_2)) { auto numTracks = musicObj->GetTrackCount(); if (numTracks != 0) { auto track0 = musicObj->GetTrack(0); if (!track0->Asset.IsAvailable()) { continue; } } else { continue; } } if (gCheatsUnlockOperatingLimits || musicObj->SupportsRideType(ride->type)) { musicOrder.push_back(i); } } } // Sort available music by the alphabetical order std::stable_sort(musicOrder.begin(), musicOrder.end(), [](const ObjectEntryIndex& a, const ObjectEntryIndex& b) { return String::Compare(GetMusicString(b), GetMusicString(a), false) > 0; }); // Setup dropdown list auto numItems = musicOrder.size(); for (size_t i = 0; i < numItems; i++) { auto musicObj = static_cast(objManager.GetLoadedObject(ObjectType::Music, musicOrder[i])); gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL; gDropdownItems[i].Args = musicObj->NameStringId; } WindowDropdownShowTextCustomWidth( { w->windowPos.x + dropdownWidget->left, w->windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1, w->colours[1], 0, Dropdown::Flag::StayOpen, numItems, widget->right - dropdownWidget->left); // Set currently checked item for (size_t i = 0; i < numItems; i++) { if (musicOrder[i] == ride->music) { Dropdown::SetChecked(static_cast(i), true); } } } /** * * rct2: 0x006B1F03 */ static void WindowRideMusicDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex) { if (widgetIndex == WIDX_MUSIC_DROPDOWN && dropdownIndex >= 0 && static_cast(dropdownIndex) < window_ride_current_music_style_order.size()) { auto musicStyle = window_ride_current_music_style_order[dropdownIndex]; set_operating_setting(w->rideId, RideSetSetting::MusicType, musicStyle); } } /** * * rct2: 0x006B2198 */ static void WindowRideMusicUpdate(rct_window* w) { w->frame_no++; window_event_invalidate_call(w); widget_invalidate(*w, WIDX_TAB_6); } /** * * rct2: 0x006B1DEA */ static void WindowRideMusicInvalidate(rct_window* w) { auto widgets = window_ride_page_widgets[w->page]; if (w->widgets != widgets) { w->widgets = widgets; WindowInitScrollWidgets(*w); } WindowRideSetPressedTab(w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto ft = Formatter::Common(); ride->FormatNameTo(ft); // Set selected music StringId musicName = STR_NONE; auto& objManager = GetContext()->GetObjectManager(); auto musicObj = static_cast(objManager.GetLoadedObject(ObjectType::Music, ride->music)); if (musicObj != nullptr) { musicName = musicObj->NameStringId; } window_ride_music_widgets[WIDX_MUSIC].text = musicName; // Set music activated auto isMusicActivated = (ride->lifecycle_flags & RIDE_LIFECYCLE_MUSIC) != 0; if (isMusicActivated) { w->pressed_widgets |= (1ULL << WIDX_PLAY_MUSIC); w->disabled_widgets &= ~(1ULL << WIDX_MUSIC); w->disabled_widgets &= ~(1ULL << WIDX_MUSIC_DROPDOWN); } else { w->pressed_widgets &= ~(1ULL << WIDX_PLAY_MUSIC); w->disabled_widgets |= (1ULL << WIDX_MUSIC); w->disabled_widgets |= (1ULL << WIDX_MUSIC_DROPDOWN); } WindowRideAnchorBorderWidgets(w); window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10); } /** * * rct2: 0x006B1ECC */ static void WindowRideMusicPaint(rct_window* w, rct_drawpixelinfo* dpi) { WindowDrawWidgets(*w, dpi); WindowRideDrawTabImages(dpi, w); } #pragma endregion #pragma region Measurements static StringId GetRatingName(ride_rating rating) { int32_t index = std::clamp(rating >> 8, 0, static_cast(std::size(RatingNames)) - 1); return RatingNames[index]; } /** * * rct2: 0x006D2804 when al == 0*/ static void CancelScenerySelection() { gGamePaused &= ~GAME_PAUSED_SAVING_TRACK; gTrackDesignSaveMode = false; OpenRCT2::Audio::Resume(); rct_window* main_w = window_get_main(); if (main_w != nullptr) { main_w->viewport->flags &= ~(VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE); } gfx_invalidate_screen(); tool_cancel(); } /** * * rct2: 0x006D27A3 */ static void SetupScenerySelection(rct_window* w) { if (gTrackDesignSaveMode) { CancelScenerySelection(); } while (tool_set(*w, WIDX_BACKGROUND, Tool::Crosshair)) ; gTrackDesignSaveRideIndex = w->rideId; track_design_save_init(); gGamePaused |= GAME_PAUSED_SAVING_TRACK; gTrackDesignSaveMode = true; OpenRCT2::Audio::StopAll(); rct_window* w_main = window_get_main(); if (w_main != nullptr) { w_main->viewport->flags |= (VIEWPORT_FLAG_HIDE_VERTICAL | VIEWPORT_FLAG_HIDE_BASE); } gfx_invalidate_screen(); } /** * * rct2: 0x006D3026 */ static void WindowRideMeasurementsDesignReset() { track_design_save_reset_scenery(); } /** * * rct2: 0x006D303D */ static void WindowRideMeasurementsDesignSelectNearbyScenery() { track_design_save_select_nearby_scenery(gTrackDesignSaveRideIndex); } /** * * rct2: 0x006AD4DA */ void WindowRideMeasurementsDesignCancel() { if (gTrackDesignSaveMode) { CancelScenerySelection(); } } static void TrackDesignCallback(int32_t result, [[maybe_unused]] const utf8* path) { if (result == MODAL_RESULT_OK) { track_repository_scan(); } gfx_invalidate_screen(); }; /** * * rct2: 0x006AD4CD */ static void WindowRideMeasurementsDesignSave(rct_window* w) { TrackDesignState tds{}; Ride* ride = get_ride(w->rideId); _trackDesign = ride->SaveToTrackDesign(tds); if (!_trackDesign) { return; } if (gTrackDesignSaveMode) { auto errMessage = _trackDesign->CreateTrackDesignScenery(tds); if (errMessage != STR_NONE) { context_show_error(STR_CANT_SAVE_TRACK_DESIGN, errMessage, {}); return; } } auto trackName = ride->GetName(); auto intent = Intent(WindowClass::Loadsave); intent.putExtra(INTENT_EXTRA_LOADSAVE_TYPE, LOADSAVETYPE_SAVE | LOADSAVETYPE_TRACK); intent.putExtra(INTENT_EXTRA_TRACK_DESIGN, _trackDesign.get()); intent.putExtra(INTENT_EXTRA_PATH, trackName); intent.putExtra(INTENT_EXTRA_CALLBACK, reinterpret_cast(&TrackDesignCallback)); context_open_intent(&intent); } /** * * rct2: 0x006AD4DA */ static void WindowRideMeasurementsClose(rct_window* w) { WindowRideMeasurementsDesignCancel(); } /** * * rct2: 0x006AD478 */ static void WindowRideMeasurementsMouseup(rct_window* w, WidgetIndex widgetIndex) { switch (widgetIndex) { case WIDX_CLOSE: window_close(*w); break; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: case WIDX_TAB_5: case WIDX_TAB_6: case WIDX_TAB_7: case WIDX_TAB_8: case WIDX_TAB_9: case WIDX_TAB_10: WindowRideSetPage(w, widgetIndex - WIDX_TAB_1); break; case WIDX_SELECT_NEARBY_SCENERY: WindowRideMeasurementsDesignSelectNearbyScenery(); break; case WIDX_RESET_SELECTION: WindowRideMeasurementsDesignReset(); break; case WIDX_SAVE_DESIGN: WindowRideMeasurementsDesignSave(w); break; case WIDX_CANCEL_DESIGN: WindowRideMeasurementsDesignCancel(); break; } } /** * * rct2: 0x006AD564 */ static void WindowRideMeasurementsResize(rct_window* w) { window_set_resize(*w, 316, 234, 316, 234); } /** * * rct2: 0x006AD4AB */ static void WindowRideMeasurementsMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget) { if (widgetIndex != WIDX_SAVE_TRACK_DESIGN) return; auto ride = get_ride(w->rideId); if (ride == nullptr) return; gDropdownItems[0].Format = STR_SAVE_TRACK_DESIGN_ITEM; gDropdownItems[1].Format = STR_SAVE_TRACK_DESIGN_WITH_SCENERY_ITEM; WindowDropdownShowText( { w->windowPos.x + widget->left, w->windowPos.y + widget->top }, widget->height() + 1, w->colours[1], Dropdown::Flag::StayOpen, 2); gDropdownDefaultIndex = 0; if (!ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK)) { // Disable saving without scenery if we're a flat ride Dropdown::SetDisabled(0, true); gDropdownDefaultIndex = 1; } } /** * * rct2: 0x006AD4B2 */ static void WindowRideMeasurementsDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex) { if (widgetIndex != WIDX_SAVE_TRACK_DESIGN) return; if (dropdownIndex == -1) dropdownIndex = gDropdownHighlightedIndex; if (dropdownIndex == 0) { WindowRideMeasurementsDesignSave(w); } else SetupScenerySelection(w); } /** * * rct2: 0x006AD5DD */ static void WindowRideMeasurementsUpdate(rct_window* w) { w->frame_no++; window_event_invalidate_call(w); widget_invalidate(*w, WIDX_TAB_7); } /** * * rct2: 0x006D2AE7 */ static void WindowRideMeasurementsTooldown(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) { _lastSceneryX = screenCoords.x; _lastSceneryY = screenCoords.y; _collectTrackDesignScenery = true; // Default to true in case user does not select anything valid constexpr auto flags = EnumsToFlags( ViewportInteractionItem::Scenery, ViewportInteractionItem::Footpath, ViewportInteractionItem::Wall, ViewportInteractionItem::LargeScenery); auto info = get_map_coordinates_from_pos(screenCoords, flags); switch (info.SpriteType) { case ViewportInteractionItem::Scenery: case ViewportInteractionItem::LargeScenery: case ViewportInteractionItem::Wall: case ViewportInteractionItem::Footpath: _collectTrackDesignScenery = !track_design_save_contains_tile_element(info.Element); track_design_save_select_tile_element(info.SpriteType, info.Loc, info.Element, _collectTrackDesignScenery); break; default: break; } } static void WindowRideMeasurementsTooldrag(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) { if (screenCoords.x == _lastSceneryX && screenCoords.y == _lastSceneryY) return; _lastSceneryX = screenCoords.x; _lastSceneryY = screenCoords.y; auto flags = EnumsToFlags( ViewportInteractionItem::Scenery, ViewportInteractionItem::Footpath, ViewportInteractionItem::Wall, ViewportInteractionItem::LargeScenery); auto info = get_map_coordinates_from_pos(screenCoords, flags); switch (info.SpriteType) { case ViewportInteractionItem::Scenery: case ViewportInteractionItem::LargeScenery: case ViewportInteractionItem::Wall: case ViewportInteractionItem::Footpath: track_design_save_select_tile_element(info.SpriteType, info.Loc, info.Element, _collectTrackDesignScenery); break; default: break; } } /** * * rct2: 0x006AD4DA */ static void WindowRideMeasurementsToolabort(rct_window* w, WidgetIndex widgetIndex) { WindowRideMeasurementsDesignCancel(); } /** * * rct2: 0x006ACDBC */ static void WindowRideMeasurementsInvalidate(rct_window* w) { auto widgets = window_ride_page_widgets[w->page]; if (w->widgets != widgets) { w->widgets = widgets; WindowInitScrollWidgets(*w); } WindowRideSetPressedTab(w); const auto rideId = w->rideId; auto ride = get_ride(rideId); if (ride == nullptr) return; auto ft = Formatter::Common(); ride->FormatNameTo(ft); window_ride_measurements_widgets[WIDX_SAVE_TRACK_DESIGN].tooltip = STR_SAVE_TRACK_DESIGN_NOT_POSSIBLE; window_ride_measurements_widgets[WIDX_SAVE_TRACK_DESIGN].type = WindowWidgetType::Empty; if (gTrackDesignSaveMode && gTrackDesignSaveRideIndex == rideId) { window_ride_measurements_widgets[WIDX_SELECT_NEARBY_SCENERY].type = WindowWidgetType::Button; window_ride_measurements_widgets[WIDX_RESET_SELECTION].type = WindowWidgetType::Button; window_ride_measurements_widgets[WIDX_SAVE_DESIGN].type = WindowWidgetType::Button; window_ride_measurements_widgets[WIDX_CANCEL_DESIGN].type = WindowWidgetType::Button; } else { window_ride_measurements_widgets[WIDX_SELECT_NEARBY_SCENERY].type = WindowWidgetType::Empty; window_ride_measurements_widgets[WIDX_RESET_SELECTION].type = WindowWidgetType::Empty; window_ride_measurements_widgets[WIDX_SAVE_DESIGN].type = WindowWidgetType::Empty; window_ride_measurements_widgets[WIDX_CANCEL_DESIGN].type = WindowWidgetType::Empty; window_ride_measurements_widgets[WIDX_SAVE_TRACK_DESIGN].type = WindowWidgetType::FlatBtn; w->disabled_widgets |= (1ULL << WIDX_SAVE_TRACK_DESIGN); if (ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED) { if (ride->excitement != RIDE_RATING_UNDEFINED) { w->disabled_widgets &= ~(1ULL << WIDX_SAVE_TRACK_DESIGN); window_ride_measurements_widgets[WIDX_SAVE_TRACK_DESIGN].tooltip = STR_SAVE_TRACK_DESIGN; } } } WindowRideAnchorBorderWidgets(w); window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10); } /** * * rct2: 0x006ACF07 */ static void WindowRideMeasurementsPaint(rct_window* w, rct_drawpixelinfo* dpi) { WindowDrawWidgets(*w, dpi); WindowRideDrawTabImages(dpi, w); if (window_ride_measurements_widgets[WIDX_SAVE_DESIGN].type == WindowWidgetType::Button) { rct_widget* widget = &window_ride_measurements_widgets[WIDX_PAGE_BACKGROUND]; ScreenCoordsXY widgetCoords(w->windowPos.x + widget->width() / 2, w->windowPos.y + widget->top + 40); DrawTextWrapped(dpi, widgetCoords, w->width - 8, STR_CLICK_ITEMS_OF_SCENERY_TO_SELECT, {}, { TextAlignment::CENTRE }); widgetCoords.x = w->windowPos.x + 4; widgetCoords.y = w->windowPos.y + window_ride_measurements_widgets[WIDX_SELECT_NEARBY_SCENERY].bottom + 17; gfx_fill_rect_inset( dpi, { widgetCoords, { w->windowPos.x + 312, widgetCoords.y + 1 } }, w->colours[1], INSET_RECT_FLAG_BORDER_INSET); } else { auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto screenCoords = w->windowPos + ScreenCoordsXY{ window_ride_measurements_widgets[WIDX_PAGE_BACKGROUND].left + 4, window_ride_measurements_widgets[WIDX_PAGE_BACKGROUND].top + 4 }; if (ride->lifecycle_flags & RIDE_LIFECYCLE_TESTED) { // Excitement StringId ratingName = GetRatingName(ride->excitement); auto ft = Formatter(); ft.Add(ride->excitement); ft.Add(ratingName); StringId stringId = !ride_has_ratings(ride) ? STR_EXCITEMENT_RATING_NOT_YET_AVAILABLE : STR_EXCITEMENT_RATING; DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += LIST_ROW_HEIGHT; // Intensity ratingName = GetRatingName(ride->intensity); ft = Formatter(); ft.Add(ride->intensity); ft.Add(ratingName); stringId = STR_INTENSITY_RATING; if (!ride_has_ratings(ride)) stringId = STR_INTENSITY_RATING_NOT_YET_AVAILABLE; else if (ride->intensity >= RIDE_RATING(10, 00)) stringId = STR_INTENSITY_RATING_RED; DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += LIST_ROW_HEIGHT; // Nausea ratingName = GetRatingName(ride->nausea); ft = Formatter(); ft.Add(ride->nausea); ft.Add(ratingName); stringId = !ride_has_ratings(ride) ? STR_NAUSEA_RATING_NOT_YET_AVAILABLE : STR_NAUSEA_RATING; DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += 2 * LIST_ROW_HEIGHT; // Horizontal rule gfx_fill_rect_inset( dpi, { screenCoords - ScreenCoordsXY{ 0, 6 }, screenCoords + ScreenCoordsXY{ 303, -5 } }, w->colours[1], INSET_RECT_FLAG_BORDER_INSET); if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_NO_RAW_STATS)) { if (ride->type == RIDE_TYPE_MINI_GOLF) { // Holes ft = Formatter(); ft.Add(ride->holes); DrawTextBasic(dpi, screenCoords, STR_HOLES, ft); screenCoords.y += LIST_ROW_HEIGHT; } else { // Max speed ft = Formatter(); ft.Add((ride->max_speed * 9) >> 18); DrawTextBasic(dpi, screenCoords, STR_MAX_SPEED, ft); screenCoords.y += LIST_ROW_HEIGHT; // Average speed ft = Formatter(); ft.Add((ride->average_speed * 9) >> 18); DrawTextBasic(dpi, screenCoords, STR_AVERAGE_SPEED, ft); screenCoords.y += LIST_ROW_HEIGHT; // Ride time ft = Formatter(); int32_t numTimes = 0; for (int32_t i = 0; i < ride->num_stations; i++) { StationIndex stationIndex = StationIndex::FromUnderlying(numTimes); auto time = ride->GetStation(stationIndex).SegmentTime; if (time != 0) { ft.Add(STR_RIDE_TIME_ENTRY_WITH_SEPARATOR); ft.Add(time); numTimes++; } } if (numTimes == 0) { ft.Add(STR_RIDE_TIME_ENTRY); ft.Add(0); numTimes++; } else { // sadly, STR_RIDE_TIME_ENTRY_WITH_SEPARATOR are defined with the separator AFTER an entry // therefore we set the last entry to use the no-separator format now, post-format ft.Rewind(); ft.Increment((numTimes - 1) * 4); ft.Add(STR_RIDE_TIME_ENTRY); } ft.Rewind(); ft.Increment(numTimes * 4); ft.Add(0); ft.Add(0); ft.Add(0); ft.Add(0); DrawTextEllipsised(dpi, screenCoords, 308, STR_RIDE_TIME, ft); screenCoords.y += LIST_ROW_HEIGHT; } // Ride length ft = Formatter(); int32_t numLengths = 0; for (int32_t i = 0; i < ride->num_stations; i++) { StationIndex stationIndex = StationIndex::FromUnderlying(i); auto length = ride->GetStation(stationIndex).SegmentLength; if (length != 0) { length >>= 16; ft.Add(STR_RIDE_LENGTH_ENTRY_WITH_SEPARATOR); ft.Add(length & 0xFFFF); numLengths++; } } if (numLengths == 0) { ft.Add(STR_RIDE_LENGTH_ENTRY); ft.Add(0); numLengths++; } else { // sadly, STR_RIDE_LENGTH_ENTRY_WITH_SEPARATOR are defined with the separator AFTER an entry // therefore we set the last entry to use the no-separator format now, post-format ft.Rewind(); ft.Increment((numLengths - 1) * 4); ft.Add(STR_RIDE_LENGTH_ENTRY); } ft.Rewind(); ft.Increment(numLengths * 4); ft.Add(0); ft.Add(0); ft.Add(0); ft.Add(0); DrawTextEllipsised(dpi, screenCoords, 308, STR_RIDE_LENGTH, ft); screenCoords.y += LIST_ROW_HEIGHT; if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_G_FORCES)) { // Max. positive vertical G's stringId = STR_MAX_POSITIVE_VERTICAL_G; ft = Formatter(); ft.Add(ride->max_positive_vertical_g); DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += LIST_ROW_HEIGHT; // Max. negative vertical G's stringId = ride->max_negative_vertical_g <= RIDE_G_FORCES_RED_NEG_VERTICAL ? STR_MAX_NEGATIVE_VERTICAL_G_RED : STR_MAX_NEGATIVE_VERTICAL_G; ft = Formatter(); ft.Add(ride->max_negative_vertical_g); DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += LIST_ROW_HEIGHT; // Max lateral G's stringId = ride->max_lateral_g > RIDE_G_FORCES_RED_LATERAL ? STR_MAX_LATERAL_G_RED : STR_MAX_LATERAL_G; ft = Formatter(); ft.Add(ride->max_lateral_g); DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += LIST_ROW_HEIGHT; // Total 'air' time ft = Formatter(); ft.Add(ride->total_air_time * 3); DrawTextBasic(dpi, screenCoords, STR_TOTAL_AIR_TIME, ft); screenCoords.y += LIST_ROW_HEIGHT; } if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_DROPS)) { // Drops auto drops = ride->drops & 0x3F; ft = Formatter(); ft.Add(drops); DrawTextBasic(dpi, screenCoords, STR_DROPS, ft); screenCoords.y += LIST_ROW_HEIGHT; // Highest drop height auto highestDropHeight = (ride->highest_drop_height * 3) / 4; ft = Formatter(); ft.Add(highestDropHeight); DrawTextBasic(dpi, screenCoords, STR_HIGHEST_DROP_HEIGHT, ft); screenCoords.y += LIST_ROW_HEIGHT; } if (ride->type != RIDE_TYPE_MINI_GOLF) { // Inversions if (ride->inversions != 0) { ft = Formatter(); ft.Add(ride->inversions); DrawTextBasic(dpi, screenCoords, STR_INVERSIONS, ft); screenCoords.y += LIST_ROW_HEIGHT; } } } } else { DrawTextBasic(dpi, screenCoords, STR_NO_TEST_RESULTS_YET); } } } #pragma endregion #pragma region Graphs enum { GRAPH_VELOCITY, GRAPH_ALTITUDE, GRAPH_VERTICAL, GRAPH_LATERAL }; /** * * rct2: 0x006AE8A6 */ static void WindowRideSetGraph(rct_window* w, int32_t type) { if ((w->list_information_type & 0xFF) == type) { w->list_information_type ^= 0x8000; } else { w->list_information_type &= 0xFF00; w->list_information_type |= type; } w->Invalidate(); } /** * * rct2: 0x006AE85D */ static void WindowRideGraphsMouseup(rct_window* w, WidgetIndex widgetIndex) { switch (widgetIndex) { case WIDX_CLOSE: window_close(*w); break; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: case WIDX_TAB_5: case WIDX_TAB_6: case WIDX_TAB_7: case WIDX_TAB_8: case WIDX_TAB_9: case WIDX_TAB_10: WindowRideSetPage(w, widgetIndex - WIDX_TAB_1); break; } } /** * * rct2: 0x006AE8DA */ static void WindowRideGraphsResize(rct_window* w) { window_set_resize(*w, 316, 182, 500, 450); } /** * * rct2: 0x006AE878 */ static void WindowRideGraphsMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget) { switch (widgetIndex) { case WIDX_GRAPH_VELOCITY: WindowRideSetGraph(w, GRAPH_VELOCITY); break; case WIDX_GRAPH_ALTITUDE: WindowRideSetGraph(w, GRAPH_ALTITUDE); break; case WIDX_GRAPH_VERTICAL: WindowRideSetGraph(w, GRAPH_VERTICAL); break; case WIDX_GRAPH_LATERAL: WindowRideSetGraph(w, GRAPH_LATERAL); break; } } /** * * rct2: 0x006AE95D */ static void WindowRideGraphsUpdate(rct_window* w) { rct_widget* widget; int32_t x; w->frame_no++; window_event_invalidate_call(w); widget_invalidate(*w, WIDX_TAB_8); window_event_invalidate_call(w); widget_invalidate(*w, WIDX_GRAPH); widget = &window_ride_graphs_widgets[WIDX_GRAPH]; x = w->scrolls[0].h_left; if (!(w->list_information_type & 0x8000)) { auto ride = get_ride(w->rideId); if (ride != nullptr) { RideMeasurement* measurement{}; std::tie(measurement, std::ignore) = ride->GetMeasurement(); x = measurement == nullptr ? 0 : measurement->current_item - ((widget->width() / 4) * 3); } } w->scrolls[0].h_left = std::clamp(x, 0, w->scrolls[0].h_right - (widget->width() - 2)); WidgetScrollUpdateThumbs(*w, WIDX_GRAPH); } /** * * rct2: 0x006AEA75 */ static void WindowRideGraphsScrollgetheight(rct_window* w, int32_t scrollIndex, int32_t* width, int32_t* height) { window_event_invalidate_call(w); // Set minimum size *width = window_ride_graphs_widgets[WIDX_GRAPH].width() - 2; // Get measurement size auto ride = get_ride(w->rideId); if (ride != nullptr) { RideMeasurement* measurement{}; std::tie(measurement, std::ignore) = ride->GetMeasurement(); if (measurement != nullptr) { *width = std::max(*width, measurement->num_items); } } } /** * * rct2: 0x006AE953 */ static void WindowRideGraphs15(rct_window* w, int32_t scrollIndex, int32_t scrollAreaType) { w->list_information_type |= 0x8000; } /** * * rct2: 0x006AEA05 */ static OpenRCT2String WindowRideGraphsTooltip(rct_window* w, const WidgetIndex widgetIndex, const StringId fallback) { if (widgetIndex == WIDX_GRAPH) { auto ride = get_ride(w->rideId); if (ride != nullptr) { auto [measurement, message] = ride->GetMeasurement(); if (measurement != nullptr && (measurement->flags & RIDE_MEASUREMENT_FLAG_RUNNING)) { auto ft = Formatter(); ft.Increment(2); ft.Add(GetRideComponentName(ride->GetRideTypeDescriptor().NameConvention.vehicle).number); ft.Add(measurement->vehicle_index + 1); return { fallback, ft }; } return message; } } else { return { STR_NONE, {} }; } return { fallback, {} }; } /** * * rct2: 0x006AE372 */ static void WindowRideGraphsInvalidate(rct_window* w) { auto widgets = window_ride_page_widgets[w->page]; if (w->widgets != widgets) { w->widgets = widgets; WindowInitScrollWidgets(*w); } WindowRideSetPressedTab(w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto ft = Formatter::Common(); ride->FormatNameTo(ft); // Set pressed graph button type w->pressed_widgets &= ~(1ULL << WIDX_GRAPH_VELOCITY); w->pressed_widgets &= ~(1ULL << WIDX_GRAPH_ALTITUDE); w->pressed_widgets &= ~(1ULL << WIDX_GRAPH_VERTICAL); w->pressed_widgets &= ~(1ULL << WIDX_GRAPH_LATERAL); w->pressed_widgets |= (1LL << (WIDX_GRAPH_VELOCITY + (w->list_information_type & 0xFF))); // Hide graph buttons that are not applicable if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_G_FORCES)) { window_ride_graphs_widgets[WIDX_GRAPH_VERTICAL].type = WindowWidgetType::Button; window_ride_graphs_widgets[WIDX_GRAPH_LATERAL].type = WindowWidgetType::Button; } else { window_ride_graphs_widgets[WIDX_GRAPH_VERTICAL].type = WindowWidgetType::Empty; window_ride_graphs_widgets[WIDX_GRAPH_LATERAL].type = WindowWidgetType::Empty; } // Anchor graph widget auto x = w->width - 4; auto y = w->height - BUTTON_FACE_HEIGHT - 8; window_ride_graphs_widgets[WIDX_GRAPH].right = x; window_ride_graphs_widgets[WIDX_GRAPH].bottom = y; y += 3; window_ride_graphs_widgets[WIDX_GRAPH_VELOCITY].top = y; window_ride_graphs_widgets[WIDX_GRAPH_ALTITUDE].top = y; window_ride_graphs_widgets[WIDX_GRAPH_VERTICAL].top = y; window_ride_graphs_widgets[WIDX_GRAPH_LATERAL].top = y; y += BUTTON_FACE_HEIGHT + 1; window_ride_graphs_widgets[WIDX_GRAPH_VELOCITY].bottom = y; window_ride_graphs_widgets[WIDX_GRAPH_ALTITUDE].bottom = y; window_ride_graphs_widgets[WIDX_GRAPH_VERTICAL].bottom = y; window_ride_graphs_widgets[WIDX_GRAPH_LATERAL].bottom = y; WindowRideAnchorBorderWidgets(w); window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10); } /** * * rct2: 0x006AE4BC */ static void WindowRideGraphsPaint(rct_window* w, rct_drawpixelinfo* dpi) { WindowDrawWidgets(*w, dpi); WindowRideDrawTabImages(dpi, w); } /** * * rct2: 0x006AE4C7 */ static void WindowRideGraphsScrollpaint(rct_window* w, rct_drawpixelinfo* dpi, int32_t scrollIndex) { gfx_clear(dpi, ColourMapA[COLOUR_SATURATED_GREEN].darker); auto widget = &window_ride_graphs_widgets[WIDX_GRAPH]; auto ride = get_ride(w->rideId); if (ride == nullptr) { return; } auto [measurement, message] = ride->GetMeasurement(); if (measurement == nullptr) { // No measurement message ScreenCoordsXY stringCoords(widget->width() / 2, widget->height() / 2 - 5); int32_t width = widget->width() - 2; DrawTextWrapped(dpi, stringCoords, width, message.str, message.args, { TextAlignment::CENTRE }); return; } // Vertical grid lines const uint8_t lightColour = ColourMapA[COLOUR_SATURATED_GREEN].mid_light; const uint8_t darkColour = ColourMapA[COLOUR_SATURATED_GREEN].mid_dark; int32_t time = 0; for (int32_t x = 0; x < dpi->x + dpi->width; x += 80) { if (x + 80 >= dpi->x) { auto coord1 = ScreenCoordsXY{ x, dpi->y }; auto coord2 = ScreenCoordsXY{ x, dpi->y + dpi->height - 1 }; gfx_fill_rect(dpi, { coord1, coord2 }, lightColour); gfx_fill_rect(dpi, { coord1 + ScreenCoordsXY{ 16, 0 }, coord2 + ScreenCoordsXY{ 16, 0 } }, darkColour); gfx_fill_rect(dpi, { coord1 + ScreenCoordsXY{ 32, 0 }, coord2 + ScreenCoordsXY{ 32, 0 } }, darkColour); gfx_fill_rect(dpi, { coord1 + ScreenCoordsXY{ 48, 0 }, coord2 + ScreenCoordsXY{ 48, 0 } }, darkColour); gfx_fill_rect(dpi, { coord1 + ScreenCoordsXY{ 64, 0 }, coord2 + ScreenCoordsXY{ 64, 0 } }, darkColour); } time += 5; } // Horizontal grid lines int32_t listType = w->list_information_type & 0xFF; int16_t yUnit = window_graphs_y_axi[listType].unit; StringId stringID = window_graphs_y_axi[listType].label; int16_t yUnitInterval = window_graphs_y_axi[listType].unit_interval; int16_t yInterval = window_graphs_y_axi[listType].interval; // Scale modifier if (listType == GRAPH_ALTITUDE) { yUnit -= gMapBaseZ * 3; } for (int32_t y = widget->height() - 13; y >= 8; y -= yInterval, yUnit += yUnitInterval) { // Minor / major line int32_t colour = yUnit == 0 ? lightColour : darkColour; gfx_fill_rect(dpi, { { dpi->x, y }, { dpi->x + dpi->width - 1, y } }, colour); int16_t scaled_yUnit = yUnit; // Scale modifier if (listType == GRAPH_ALTITUDE) scaled_yUnit /= 2; auto ft = Formatter(); ft.Add(scaled_yUnit); DrawTextBasic(dpi, { w->scrolls[0].h_left + 1, y - 4 }, stringID, ft, { FontSpriteBase::SMALL }); } // Time marks time = 0; for (int32_t x = 0; x < dpi->x + dpi->width; x += 80) { auto ft = Formatter(); ft.Add(time); if (x + 80 >= dpi->x) DrawTextBasic(dpi, { x + 2, 1 }, STR_RIDE_STATS_TIME, ft, { FontSpriteBase::SMALL }); time += 5; } // Plot int32_t x = dpi->x; int32_t firstPoint, secondPoint; // Uses the force limits (used to draw extreme G's in red on measurement tab) to determine if line should be drawn red. int32_t intensityThresholdPositive = 0; int32_t intensityThresholdNegative = 0; for (int32_t width = 0; width < dpi->width; width++, x++) { if (x < 0 || x >= measurement->num_items - 1) continue; constexpr int32_t VerticalGraphHeightOffset = 39; constexpr int32_t LateralGraphHeightOffset = 52; switch (listType) { case GRAPH_VELOCITY: firstPoint = measurement->velocity[x] / 2; secondPoint = measurement->velocity[x + 1] / 2; break; case GRAPH_ALTITUDE: firstPoint = measurement->altitude[x]; secondPoint = measurement->altitude[x + 1]; break; case GRAPH_VERTICAL: firstPoint = measurement->vertical[x] + VerticalGraphHeightOffset; secondPoint = measurement->vertical[x + 1] + VerticalGraphHeightOffset; intensityThresholdNegative = (RIDE_G_FORCES_RED_NEG_VERTICAL / 8) + VerticalGraphHeightOffset; break; case GRAPH_LATERAL: firstPoint = measurement->lateral[x] + LateralGraphHeightOffset; secondPoint = measurement->lateral[x + 1] + LateralGraphHeightOffset; intensityThresholdPositive = (RIDE_G_FORCES_RED_LATERAL / 8) + LateralGraphHeightOffset; intensityThresholdNegative = -(RIDE_G_FORCES_RED_LATERAL / 8) + LateralGraphHeightOffset; break; default: log_error("Wrong graph type %d", listType); firstPoint = secondPoint = 0; break; } // Adjust line to match graph widget position. firstPoint = widget->height() - firstPoint - 13; secondPoint = widget->height() - secondPoint - 13; if (firstPoint > secondPoint) { std::swap(firstPoint, secondPoint); } // Adjust threshold line position as well if (listType == GRAPH_VERTICAL || listType == GRAPH_LATERAL) { intensityThresholdPositive = widget->height() - intensityThresholdPositive - 13; intensityThresholdNegative = widget->height() - intensityThresholdNegative - 13; } const bool previousMeasurement = x > measurement->current_item; // Draw the current line in grey. gfx_fill_rect( dpi, { { x, firstPoint }, { x, secondPoint } }, previousMeasurement ? PALETTE_INDEX_17 : PALETTE_INDEX_21); // Draw red over extreme values (if supported by graph type). if (listType == GRAPH_VERTICAL || listType == GRAPH_LATERAL) { const auto redLineColour = previousMeasurement ? PALETTE_INDEX_171 : PALETTE_INDEX_173; // Line exceeds negative threshold (at bottom of graph). if (secondPoint >= intensityThresholdNegative) { const auto redLineTop = ScreenCoordsXY{ x, std::max(firstPoint, intensityThresholdNegative) }; const auto redLineBottom = ScreenCoordsXY{ x, std::max(secondPoint, intensityThresholdNegative) }; gfx_fill_rect(dpi, { redLineTop, redLineBottom }, redLineColour); } // Line exceeds positive threshold (at top of graph). if (listType == GRAPH_LATERAL && firstPoint < intensityThresholdPositive) { const auto redLineTop = ScreenCoordsXY{ x, std::min(firstPoint, intensityThresholdPositive) }; const auto redLineBottom = ScreenCoordsXY{ x, std::min(secondPoint, intensityThresholdPositive) }; gfx_fill_rect(dpi, { redLineTop, redLineBottom }, redLineColour); } } } } #pragma endregion #pragma region Income static utf8 _moneyInputText[MONEY_STRING_MAXLENGTH]; static void UpdateSamePriceThroughoutFlags(ShopItem shop_item) { if (GetShopItemDescriptor(shop_item).IsPhoto()) { auto newFlags = gSamePriceThroughoutPark; if (gSamePriceThroughoutPark & EnumToFlag(shop_item)) newFlags &= ~EnumsToFlags(ShopItem::Photo, ShopItem::Photo2, ShopItem::Photo3, ShopItem::Photo4); else newFlags |= EnumsToFlags(ShopItem::Photo, ShopItem::Photo2, ShopItem::Photo3, ShopItem::Photo4); auto parkSetParameter = ParkSetParameterAction(ParkParameter::SamePriceInPark, newFlags); GameActions::Execute(&parkSetParameter); } else { auto newFlags = gSamePriceThroughoutPark; newFlags ^= EnumToFlag(shop_item); auto parkSetParameter = ParkSetParameterAction(ParkParameter::SamePriceInPark, newFlags); GameActions::Execute(&parkSetParameter); } } /** * * rct2: 0x006ADEFD */ static void WindowRideIncomeTogglePrimaryPrice(rct_window* w) { const auto rideId = w->rideId; auto ride = get_ride(rideId); if (ride == nullptr) return; ShopItem shop_item; const auto& rtd = ride->GetRideTypeDescriptor(); if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_TOILET)) { shop_item = ShopItem::Admission; } else { auto rideEntry = get_ride_entry(ride->subtype); if (rideEntry != nullptr) { shop_item = rideEntry->shop_item[0]; if (shop_item == ShopItem::None) return; } else { return; } } UpdateSamePriceThroughoutFlags(shop_item); auto rideSetPriceAction = RideSetPriceAction(rideId, ride->price[0], true); GameActions::Execute(&rideSetPriceAction); } /** * * rct2: 0x006AE06E */ static void WindowRideIncomeToggleSecondaryPrice(rct_window* w) { const auto rideId = w->rideId; auto ride = get_ride(rideId); if (ride == nullptr) return; auto rideEntry = get_ride_entry(ride->subtype); if (rideEntry == nullptr) return; auto shop_item = rideEntry->shop_item[1]; if (shop_item == ShopItem::None) shop_item = ride->GetRideTypeDescriptor().PhotoItem; UpdateSamePriceThroughoutFlags(shop_item); auto rideSetPriceAction = RideSetPriceAction(rideId, ride->price[1], false); GameActions::Execute(&rideSetPriceAction); } static void WindowRideIncomeSetPrimaryPrice(rct_window* w, money16 price) { auto rideSetPriceAction = RideSetPriceAction(w->rideId, price, true); GameActions::Execute(&rideSetPriceAction); } /** * * rct2: 0x006AE1E4 */ static void WindowRideIncomeIncreasePrimaryPrice(rct_window* w) { if (!WindowRideIncomeCanModifyPrimaryPrice(w)) return; auto ride = get_ride(w->rideId); if (ride == nullptr) return; money16 price = ride->price[0]; if (price < 20.00_GBP) price++; WindowRideIncomeSetPrimaryPrice(w, price); } /** * * rct2: 0x006AE237 */ static void WindowRideIncomeDecreasePrimaryPrice(rct_window* w) { if (!WindowRideIncomeCanModifyPrimaryPrice(w)) return; auto ride = get_ride(w->rideId); if (ride == nullptr) return; money16 price = ride->price[0]; if (price > 0.00_GBP) price--; WindowRideIncomeSetPrimaryPrice(w, price); } static money16 WindowRideIncomeGetSecondaryPrice(rct_window* w) { auto ride = get_ride(w->rideId); if (ride == nullptr) return 0; money16 price = ride->price[1]; return price; } static void WindowRideIncomeSetSecondaryPrice(rct_window* w, money16 price) { auto rideSetPriceAction = RideSetPriceAction(w->rideId, price, false); GameActions::Execute(&rideSetPriceAction); } static bool WindowRideIncomeCanModifyPrimaryPrice(rct_window* w) { auto ride = get_ride(w->rideId); if (ride == nullptr) return false; auto rideEntry = ride->GetRideEntry(); const auto& rtd = ride->GetRideTypeDescriptor(); return ParkRidePricesUnlocked() || rtd.HasFlag(RIDE_TYPE_FLAG_IS_TOILET) || (rideEntry != nullptr && rideEntry->shop_item[0] != ShopItem::None); } /** * * rct2: 0x006AE269 */ static void WindowRideIncomeIncreaseSecondaryPrice(rct_window* w) { money16 price = WindowRideIncomeGetSecondaryPrice(w); if (price < 20.00_GBP) price++; WindowRideIncomeSetSecondaryPrice(w, price); } /** * * rct2: 0x006AE28D */ static void WindowRideIncomeDecreaseSecondaryPrice(rct_window* w) { money16 price = WindowRideIncomeGetSecondaryPrice(w); if (price > 0.00_GBP) price--; WindowRideIncomeSetSecondaryPrice(w, price); } /** * * rct2: 0x006ADEA9 */ static void WindowRideIncomeMouseup(rct_window* w, WidgetIndex widgetIndex) { switch (widgetIndex) { case WIDX_CLOSE: window_close(*w); break; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: case WIDX_TAB_5: case WIDX_TAB_6: case WIDX_TAB_7: case WIDX_TAB_8: case WIDX_TAB_9: case WIDX_TAB_10: WindowRideSetPage(w, widgetIndex - WIDX_TAB_1); break; case WIDX_PRIMARY_PRICE: { if (!WindowRideIncomeCanModifyPrimaryPrice(w)) return; auto ride = get_ride(w->rideId); if (ride != nullptr) { money_to_string(static_cast(ride->price[0]), _moneyInputText, MONEY_STRING_MAXLENGTH, true); WindowTextInputRawOpen( w, WIDX_PRIMARY_PRICE, STR_ENTER_NEW_VALUE, STR_ENTER_NEW_VALUE, {}, _moneyInputText, MONEY_STRING_MAXLENGTH); } break; } case WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK: WindowRideIncomeTogglePrimaryPrice(w); break; case WIDX_SECONDARY_PRICE: { money32 price32 = static_cast(WindowRideIncomeGetSecondaryPrice(w)); money_to_string(price32, _moneyInputText, MONEY_STRING_MAXLENGTH, true); WindowTextInputRawOpen( w, WIDX_SECONDARY_PRICE, STR_ENTER_NEW_VALUE, STR_ENTER_NEW_VALUE, {}, _moneyInputText, MONEY_STRING_MAXLENGTH); } break; case WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK: WindowRideIncomeToggleSecondaryPrice(w); break; } } /** * * rct2: 0x006AE2F8 */ static void WindowRideIncomeResize(rct_window* w) { window_set_resize(*w, 316, 194, 316, 194); } /** * * rct2: 0x006ADED4 */ static void WindowRideIncomeMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget) { switch (widgetIndex) { case WIDX_PRIMARY_PRICE_INCREASE: WindowRideIncomeIncreasePrimaryPrice(w); break; case WIDX_PRIMARY_PRICE_DECREASE: WindowRideIncomeDecreasePrimaryPrice(w); break; case WIDX_SECONDARY_PRICE_INCREASE: WindowRideIncomeIncreaseSecondaryPrice(w); break; case WIDX_SECONDARY_PRICE_DECREASE: WindowRideIncomeDecreaseSecondaryPrice(w); break; } } /** * * rct2: 0x006AE2BF */ static void WindowRideIncomeUpdate(rct_window* w) { w->frame_no++; window_event_invalidate_call(w); widget_invalidate(*w, WIDX_TAB_9); auto ride = get_ride(w->rideId); if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_INCOME) { ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_INCOME; w->Invalidate(); } } static void WindowRideIncomeTextinput(rct_window* w, WidgetIndex widgetIndex, char* text) { if ((widgetIndex != WIDX_PRIMARY_PRICE && widgetIndex != WIDX_SECONDARY_PRICE) || text == nullptr) return; money32 price = string_to_money(text); if (price == MONEY32_UNDEFINED) { return; } price = std::clamp(price, 0.00_GBP, 20.00_GBP); money16 price16 = static_cast(price); if (widgetIndex == WIDX_PRIMARY_PRICE) { WindowRideIncomeSetPrimaryPrice(w, price16); } else { WindowRideIncomeSetSecondaryPrice(w, price16); } } /** * * rct2: 0x006ADAA3 */ static void WindowRideIncomeInvalidate(rct_window* w) { auto widgets = window_ride_page_widgets[w->page]; if (w->widgets != widgets) { w->widgets = widgets; WindowInitScrollWidgets(*w); } WindowRideSetPressedTab(w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; w->widgets[WIDX_TITLE].text = STR_ARG_18_STRINGID; auto ft = Formatter::Common(); ft.Increment(18); ride->FormatNameTo(ft); auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr) return; // Primary item w->pressed_widgets &= ~(1ULL << WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK); w->disabled_widgets &= ~(1ULL << WIDX_PRIMARY_PRICE); window_ride_income_widgets[WIDX_PRIMARY_PRICE_LABEL].tooltip = STR_NONE; window_ride_income_widgets[WIDX_PRIMARY_PRICE].tooltip = STR_NONE; // If ride prices are locked, do not allow setting the price, unless we're dealing with a shop or toilet. const auto& rtd = ride->GetRideTypeDescriptor(); if (!ParkRidePricesUnlocked() && rideEntry->shop_item[0] == ShopItem::None && !rtd.HasFlag(RIDE_TYPE_FLAG_IS_TOILET)) { w->disabled_widgets |= (1ULL << WIDX_PRIMARY_PRICE); window_ride_income_widgets[WIDX_PRIMARY_PRICE_LABEL].tooltip = STR_RIDE_INCOME_ADMISSION_PAY_FOR_ENTRY_TIP; window_ride_income_widgets[WIDX_PRIMARY_PRICE].tooltip = STR_RIDE_INCOME_ADMISSION_PAY_FOR_ENTRY_TIP; } window_ride_income_widgets[WIDX_PRIMARY_PRICE_LABEL].text = STR_RIDE_INCOME_ADMISSION_PRICE; window_ride_income_widgets[WIDX_SECONDARY_PRICE_LABEL].text = STR_SHOP_ITEM_PRICE_LABEL_ON_RIDE_PHOTO; window_ride_income_widgets[WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Empty; window_ride_income_widgets[WIDX_PRIMARY_PRICE].text = STR_BOTTOM_TOOLBAR_CASH; money16 ridePrimaryPrice = ride_get_price(ride); ft.Rewind(); ft.Add(ridePrimaryPrice); if (ridePrimaryPrice == 0) window_ride_income_widgets[WIDX_PRIMARY_PRICE].text = STR_FREE; ShopItem primaryItem = ShopItem::Admission; if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_TOILET) || ((primaryItem = rideEntry->shop_item[0]) != ShopItem::None)) { window_ride_income_widgets[WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Checkbox; if (shop_item_has_common_price(primaryItem)) w->pressed_widgets |= (1ULL << WIDX_PRIMARY_PRICE_SAME_THROUGHOUT_PARK); window_ride_income_widgets[WIDX_PRIMARY_PRICE_LABEL].text = GetShopItemDescriptor(primaryItem).Naming.PriceLabel; } // Get secondary item auto secondaryItem = ride->GetRideTypeDescriptor().PhotoItem; if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO)) { if ((secondaryItem = rideEntry->shop_item[1]) != ShopItem::None) { window_ride_income_widgets[WIDX_SECONDARY_PRICE_LABEL].text = GetShopItemDescriptor(secondaryItem) .Naming.PriceLabel; } } if (secondaryItem == ShopItem::None) { // Hide secondary item widgets window_ride_income_widgets[WIDX_SECONDARY_PRICE_LABEL].type = WindowWidgetType::Empty; window_ride_income_widgets[WIDX_SECONDARY_PRICE].type = WindowWidgetType::Empty; window_ride_income_widgets[WIDX_SECONDARY_PRICE_INCREASE].type = WindowWidgetType::Empty; window_ride_income_widgets[WIDX_SECONDARY_PRICE_DECREASE].type = WindowWidgetType::Empty; window_ride_income_widgets[WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Empty; } else { // Set same price throughout park checkbox w->pressed_widgets &= ~(1ULL << WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK); if (shop_item_has_common_price(secondaryItem)) w->pressed_widgets |= (1ULL << WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK); // Show widgets window_ride_income_widgets[WIDX_SECONDARY_PRICE_LABEL].type = WindowWidgetType::Label; window_ride_income_widgets[WIDX_SECONDARY_PRICE].type = WindowWidgetType::Spinner; window_ride_income_widgets[WIDX_SECONDARY_PRICE_INCREASE].type = WindowWidgetType::Button; window_ride_income_widgets[WIDX_SECONDARY_PRICE_DECREASE].type = WindowWidgetType::Button; window_ride_income_widgets[WIDX_SECONDARY_PRICE_SAME_THROUGHOUT_PARK].type = WindowWidgetType::Checkbox; // Set secondary item price window_ride_income_widgets[WIDX_SECONDARY_PRICE].text = STR_RIDE_SECONDARY_PRICE_VALUE; ft.Rewind(); ft.Increment(10); ft.Add(ride->price[1]); if (ride->price[1] == 0) window_ride_income_widgets[WIDX_SECONDARY_PRICE].text = STR_FREE; } WindowRideAnchorBorderWidgets(w); window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10); } /** * * rct2: 0x006ADCE5 */ static void WindowRideIncomePaint(rct_window* w, rct_drawpixelinfo* dpi) { StringId stringId; money64 profit; ShopItem primaryItem, secondaryItem; WindowDrawWidgets(*w, dpi); WindowRideDrawTabImages(dpi, w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr) return; auto screenCoords = w->windowPos + ScreenCoordsXY{ window_ride_income_widgets[WIDX_PAGE_BACKGROUND].left + 4, window_ride_income_widgets[WIDX_PAGE_BACKGROUND].top + 33 }; // Primary item profit / loss per item sold primaryItem = rideEntry->shop_item[0]; if (primaryItem != ShopItem::None) { profit = ride->price[0]; stringId = STR_PROFIT_PER_ITEM_SOLD; profit -= GetShopItemDescriptor(primaryItem).Cost; if (profit < 0) { profit *= -1; stringId = STR_LOSS_PER_ITEM_SOLD; } auto ft = Formatter(); ft.Add(profit); DrawTextBasic(dpi, screenCoords, stringId, ft); } screenCoords.y += 44; // Secondary item profit / loss per item sold secondaryItem = ride->GetRideTypeDescriptor().PhotoItem; if (!(ride->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO)) secondaryItem = rideEntry->shop_item[1]; if (secondaryItem != ShopItem::None) { profit = ride->price[1]; stringId = STR_PROFIT_PER_ITEM_SOLD; profit -= GetShopItemDescriptor(secondaryItem).Cost; if (profit < 0) { profit *= -1; stringId = STR_LOSS_PER_ITEM_SOLD; } auto ft = Formatter(); ft.Add(profit); DrawTextBasic(dpi, screenCoords, stringId, ft); } screenCoords.y += 18; // Income per hour if (ride->income_per_hour != MONEY64_UNDEFINED) { auto ft = Formatter(); ft.Add(ride->income_per_hour); DrawTextBasic(dpi, screenCoords, STR_INCOME_PER_HOUR, ft); screenCoords.y += LIST_ROW_HEIGHT; } // Running cost per hour money64 costPerHour = ride->upkeep_cost * 16; stringId = ride->upkeep_cost == MONEY16_UNDEFINED ? STR_RUNNING_COST_UNKNOWN : STR_RUNNING_COST_PER_HOUR; auto ft = Formatter(); ft.Add(costPerHour); DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += LIST_ROW_HEIGHT; // Profit per hour if (ride->profit != MONEY64_UNDEFINED) { ft = Formatter(); ft.Add(ride->profit); DrawTextBasic(dpi, screenCoords, STR_PROFIT_PER_HOUR, ft); screenCoords.y += LIST_ROW_HEIGHT; } screenCoords.y += 5; // Total profit ft = Formatter(); ft.Add(ride->total_profit); DrawTextBasic(dpi, screenCoords, STR_TOTAL_PROFIT, ft); } #pragma endregion #pragma region Customer /** * * rct2: 0x006AD986 */ static void WindowRideCustomerMouseup(rct_window* w, WidgetIndex widgetIndex) { switch (widgetIndex) { case WIDX_CLOSE: window_close(*w); break; case WIDX_TAB_1: case WIDX_TAB_2: case WIDX_TAB_3: case WIDX_TAB_4: case WIDX_TAB_5: case WIDX_TAB_6: case WIDX_TAB_7: case WIDX_TAB_8: case WIDX_TAB_9: case WIDX_TAB_10: WindowRideSetPage(w, widgetIndex - WIDX_TAB_1); break; case WIDX_SHOW_GUESTS_THOUGHTS: { auto intent = Intent(WindowClass::GuestList); intent.putExtra(INTENT_EXTRA_GUEST_LIST_FILTER, static_cast(GuestListFilterType::GuestsThinkingAboutRide)); intent.putExtra(INTENT_EXTRA_RIDE_ID, w->number); context_open_intent(&intent); break; } case WIDX_SHOW_GUESTS_ON_RIDE: { auto intent = Intent(WindowClass::GuestList); intent.putExtra(INTENT_EXTRA_GUEST_LIST_FILTER, static_cast(GuestListFilterType::GuestsOnRide)); intent.putExtra(INTENT_EXTRA_RIDE_ID, w->number); context_open_intent(&intent); break; } case WIDX_SHOW_GUESTS_QUEUING: { auto intent = Intent(WindowClass::GuestList); intent.putExtra(INTENT_EXTRA_GUEST_LIST_FILTER, static_cast(GuestListFilterType::GuestsInQueue)); intent.putExtra(INTENT_EXTRA_RIDE_ID, w->number); context_open_intent(&intent); break; } } } /** * * rct2: 0x006ADA29 */ static void WindowRideCustomerResize(rct_window* w) { w->flags |= WF_RESIZABLE; window_set_resize(*w, 316, 163, 316, 163); } /** * * rct2: 0x006AD9DD */ static void WindowRideCustomerUpdate(rct_window* w) { w->picked_peep_frame++; if (w->picked_peep_frame >= 24) w->picked_peep_frame = 0; window_event_invalidate_call(w); widget_invalidate(*w, WIDX_TAB_10); auto ride = get_ride(w->rideId); if (ride != nullptr && ride->window_invalidate_flags & RIDE_INVALIDATE_RIDE_CUSTOMER) { ride->window_invalidate_flags &= ~RIDE_INVALIDATE_RIDE_CUSTOMER; w->Invalidate(); } } /** * * rct2: 0x006AD5F8 */ static void WindowRideCustomerInvalidate(rct_window* w) { auto widgets = window_ride_page_widgets[w->page]; if (w->widgets != widgets) { w->widgets = widgets; WindowInitScrollWidgets(*w); } WindowRideSetPressedTab(w); auto ride = get_ride(w->rideId); if (ride != nullptr) { auto ft = Formatter::Common(); ride->FormatNameTo(ft); window_ride_customer_widgets[WIDX_SHOW_GUESTS_THOUGHTS].type = WindowWidgetType::FlatBtn; if (ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP_OR_FACILITY)) { window_ride_customer_widgets[WIDX_SHOW_GUESTS_ON_RIDE].type = WindowWidgetType::Empty; window_ride_customer_widgets[WIDX_SHOW_GUESTS_QUEUING].type = WindowWidgetType::Empty; } else { window_ride_customer_widgets[WIDX_SHOW_GUESTS_ON_RIDE].type = WindowWidgetType::FlatBtn; window_ride_customer_widgets[WIDX_SHOW_GUESTS_QUEUING].type = WindowWidgetType::FlatBtn; } WindowRideAnchorBorderWidgets(w); window_align_tabs(w, WIDX_TAB_1, WIDX_TAB_10); } } /** * * rct2: 0x006AD6CD */ static void WindowRideCustomerPaint(rct_window* w, rct_drawpixelinfo* dpi) { ShopItem shopItem; int16_t popularity, satisfaction, queueTime; StringId stringId; WindowDrawWidgets(*w, dpi); WindowRideDrawTabImages(dpi, w); auto ride = get_ride(w->rideId); if (ride == nullptr) return; auto screenCoords = w->windowPos + ScreenCoordsXY{ window_ride_customer_widgets[WIDX_PAGE_BACKGROUND].left + 4, window_ride_customer_widgets[WIDX_PAGE_BACKGROUND].top + 4 }; // Customers currently on ride if (ride->IsRide()) { auto ft = Formatter(); ft.Add(ride->num_riders); DrawTextBasic(dpi, screenCoords, STR_CUSTOMERS_ON_RIDE, ft); screenCoords.y += LIST_ROW_HEIGHT; } // Customers per hour auto ft = Formatter(); ft.Add(ride_customers_per_hour(ride)); DrawTextBasic(dpi, screenCoords, STR_CUSTOMERS_PER_HOUR, ft); screenCoords.y += LIST_ROW_HEIGHT; // Popularity popularity = ride->popularity; if (popularity == 255) { stringId = STR_POPULARITY_UNKNOWN; } else { stringId = STR_POPULARITY_PERCENT; popularity *= 4; } ft = Formatter(); ft.Add(popularity); DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += LIST_ROW_HEIGHT; // Satisfaction satisfaction = ride->satisfaction; if (satisfaction == 255) { stringId = STR_SATISFACTION_UNKNOWN; } else { stringId = STR_SATISFACTION_PERCENT; satisfaction *= 5; } ft = Formatter(); ft.Add(satisfaction); DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += LIST_ROW_HEIGHT; // Queue time if (ride->IsRide()) { queueTime = ride->GetMaxQueueTime(); stringId = queueTime == 1 ? STR_QUEUE_TIME_MINUTE : STR_QUEUE_TIME_MINUTES; ft = Formatter(); ft.Add(queueTime); screenCoords.y += DrawTextWrapped(dpi, screenCoords, 308, stringId, ft, { TextAlignment::LEFT }); screenCoords.y += 5; } // Primary shop items sold shopItem = ride->GetRideEntry()->shop_item[0]; if (shopItem != ShopItem::None) { ft = Formatter(); ft.Add(GetShopItemDescriptor(shopItem).Naming.Plural); ft.Add(ride->no_primary_items_sold); DrawTextBasic(dpi, screenCoords, STR_ITEMS_SOLD, ft); screenCoords.y += LIST_ROW_HEIGHT; } // Secondary shop items sold / on-ride photos sold shopItem = (ride->lifecycle_flags & RIDE_LIFECYCLE_ON_RIDE_PHOTO) ? ride->GetRideTypeDescriptor().PhotoItem : ride->GetRideEntry()->shop_item[1]; if (shopItem != ShopItem::None) { ft = Formatter(); ft.Add(GetShopItemDescriptor(shopItem).Naming.Plural); ft.Add(ride->no_secondary_items_sold); DrawTextBasic(dpi, screenCoords, STR_ITEMS_SOLD, ft); screenCoords.y += LIST_ROW_HEIGHT; } // Total customers ft = Formatter(); ft.Add(ride->total_customers); DrawTextBasic(dpi, screenCoords, STR_TOTAL_CUSTOMERS, ft); screenCoords.y += LIST_ROW_HEIGHT; // Guests favourite if (ride->IsRide()) { ft = Formatter(); ft.Add(ride->guests_favourite); stringId = ride->guests_favourite == 1 ? STR_FAVOURITE_RIDE_OF_GUEST : STR_FAVOURITE_RIDE_OF_GUESTS; DrawTextBasic(dpi, screenCoords, stringId, ft); screenCoords.y += LIST_ROW_HEIGHT; } screenCoords.y += 2; // Age // If the ride has a build date that is in the future, show it as built this year. int16_t age = std::max(date_get_year(ride->GetAge()), 0); stringId = age == 0 ? STR_BUILT_THIS_YEAR : age == 1 ? STR_BUILT_LAST_YEAR : STR_BUILT_YEARS_AGO; ft = Formatter(); ft.Add(age); DrawTextBasic(dpi, screenCoords, stringId, ft); } #pragma endregion