/***************************************************************************** * Copyright (c) 2014-2024 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 "../ride/Construction.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 using namespace OpenRCT2::TrackMetaData; namespace OpenRCT2::Ui::Windows { static constexpr StringId WINDOW_TITLE = STR_RIDE_CONSTRUCTION_WINDOW_TITLE; static constexpr int32_t WH = 394; static constexpr int32_t WW = 210; static constexpr uint16_t ARROW_PULSE_DURATION = 200; // Width of the group boxes, e.g. “Banking” static constexpr int32_t GW = WW - 6; #pragma region Widgets enum { WIDX_BACKGROUND, WIDX_TITLE, WIDX_CLOSE, WIDX_DIRECTION_GROUPBOX, WIDX_SLOPE_GROUPBOX, WIDX_BANKING_GROUPBOX, WIDX_LEFT_CURVE_VERY_SMALL, WIDX_LEFT_CURVE_SMALL, WIDX_LEFT_CURVE, WIDX_LEFT_CURVE_LARGE, WIDX_STRAIGHT, WIDX_RIGHT_CURVE_LARGE, WIDX_RIGHT_CURVE, WIDX_RIGHT_CURVE_SMALL, WIDX_RIGHT_CURVE_VERY_SMALL, WIDX_SPECIAL_TRACK_DROPDOWN, WIDX_SLOPE_DOWN_STEEP, WIDX_SLOPE_DOWN, WIDX_LEVEL, WIDX_SLOPE_UP, WIDX_SLOPE_UP_STEEP, WIDX_CHAIN_LIFT, WIDX_BANK_LEFT, WIDX_BANK_STRAIGHT, WIDX_BANK_RIGHT, WIDX_CONSTRUCT, WIDX_DEMOLISH, WIDX_PREVIOUS_SECTION, WIDX_NEXT_SECTION, WIDX_ENTRANCE_EXIT_GROUPBOX, WIDX_ENTRANCE, WIDX_EXIT, WIDX_ROTATE, WIDX_U_TRACK, WIDX_O_TRACK, WIDX_SEAT_ROTATION_GROUPBOX, WIDX_SEAT_ROTATION_ANGLE_SPINNER, WIDX_SEAT_ROTATION_ANGLE_SPINNER_UP, WIDX_SEAT_ROTATION_ANGLE_SPINNER_DOWN, WIDX_SIMULATE, WIDX_SPEED_GROUPBOX = WIDX_BANKING_GROUPBOX, WIDX_SPEED_SETTING_SPINNER = WIDX_BANK_LEFT, WIDX_SPEED_SETTING_SPINNER_UP = WIDX_BANK_STRAIGHT, WIDX_SPEED_SETTING_SPINNER_DOWN = WIDX_BANK_RIGHT, }; validate_global_widx(WC_RIDE_CONSTRUCTION, WIDX_CONSTRUCT); validate_global_widx(WC_RIDE_CONSTRUCTION, WIDX_ENTRANCE); validate_global_widx(WC_RIDE_CONSTRUCTION, WIDX_EXIT); validate_global_widx(WC_RIDE_CONSTRUCTION, WIDX_ROTATE); // clang-format off static Widget _rideConstructionWidgets[] = { WINDOW_SHIM(WINDOW_TITLE, WW, WH), MakeWidget ({ 3, 17}, { GW, 57}, WindowWidgetType::Groupbox, WindowColour::Primary , STR_RIDE_CONSTRUCTION_DIRECTION ), MakeWidget ({ 3, 76}, { GW, 41}, WindowWidgetType::Groupbox, WindowColour::Primary , STR_RIDE_CONSTRUCTION_SLOPE ), MakeWidget ({ 3, 120}, { GW, 41}, WindowWidgetType::Groupbox, WindowColour::Primary , STR_RIDE_CONSTRUCTION_ROLL_BANKING ), MakeWidget ({ 6, 29}, { 22, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_LEFT_CURVE_SMALL), STR_RIDE_CONSTRUCTION_LEFT_CURVE_VERY_SMALL_TIP ), MakeWidget ({ 28, 29}, { 22, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_G2_ICON_MEDIUM_CURVE_LEFT), STR_RIDE_CONSTRUCTION_LEFT_CURVE_SMALL_TIP ), MakeWidget ({ 50, 29}, { 22, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_LEFT_CURVE), STR_RIDE_CONSTRUCTION_LEFT_CURVE_TIP ), MakeWidget ({ 72, 29}, { 22, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_LEFT_CURVE_LARGE), STR_RIDE_CONSTRUCTION_LEFT_CURVE_LARGE_TIP ), MakeWidget ({ 94, 29}, { 22, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_STRAIGHT), STR_RIDE_CONSTRUCTION_STRAIGHT_TIP ), MakeWidget ({116, 29}, { 22, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_RIGHT_CURVE_LARGE), STR_RIDE_CONSTRUCTION_RIGHT_CURVE_LARGE_TIP ), MakeWidget ({138, 29}, { 22, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_RIGHT_CURVE), STR_RIDE_CONSTRUCTION_RIGHT_CURVE_TIP ), MakeWidget ({160, 29}, { 22, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_G2_ICON_MEDIUM_CURVE_RIGHT), STR_RIDE_CONSTRUCTION_RIGHT_CURVE_SMALL_TIP ), MakeWidget ({182, 29}, { 22, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_RIGHT_CURVE_SMALL), STR_RIDE_CONSTRUCTION_RIGHT_CURVE_VERY_SMALL_TIP ), MakeWidget ({ 6, 55}, { GW - 6, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_YELLOW_STRING, STR_RIDE_CONSTRUCTION_OTHER_TRACK_CONFIGURATIONS_TIP), MakeWidget ({ 45, 88}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_DOWN_STEEP), STR_RIDE_CONSTRUCTION_STEEP_SLOPE_DOWN_TIP ), MakeWidget ({ 69, 88}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_DOWN), STR_RIDE_CONSTRUCTION_SLOPE_DOWN_TIP ), MakeWidget ({ 93, 88}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_LEVEL), STR_RIDE_CONSTRUCTION_LEVEL_TIP ), MakeWidget ({117, 88}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_UP), STR_RIDE_CONSTRUCTION_SLOPE_UP_TIP ), MakeWidget ({141, 88}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_UP_STEEP), STR_RIDE_CONSTRUCTION_STEEP_SLOPE_UP_TIP ), MakeWidget ({178, 88}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_CHAIN_LIFT), STR_RIDE_CONSTRUCTION_CHAIN_LIFT_TIP ), MakeWidget ({ 69, 132}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_LEFT_BANK), STR_RIDE_CONSTRUCTION_ROLL_FOR_LEFT_CURVE_TIP ), MakeWidget ({ 93, 132}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_NO_BANK), STR_RIDE_CONSTRUCTION_NO_ROLL_TIP ), MakeWidget ({117, 132}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_RIGHT_BANK), STR_RIDE_CONSTRUCTION_ROLL_FOR_RIGHT_CURVE_TIP ), MakeWidget ({ 3, 164}, { GW, 170}, WindowWidgetType::ImgBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_RIDE_CONSTRUCTION_CONSTRUCT_SELECTED_SECTION_TIP), MakeWidget ({ 82, 338}, { 46, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_DEMOLISH_CURRENT_SECTION), STR_RIDE_CONSTRUCTION_REMOVE_HIGHLIGHTED_SECTION_TIP), MakeWidget ({ 52, 338}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_PREVIOUS), STR_RIDE_CONSTRUCTION_MOVE_TO_PREVIOUS_SECTION_TIP ), MakeWidget ({134, 338}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_NEXT), STR_RIDE_CONSTRUCTION_MOVE_TO_NEXT_SECTION_TIP ), MakeWidget ({ 3, 362}, { GW, 28}, WindowWidgetType::Groupbox, WindowColour::Primary ), MakeWidget ({ 31, 372}, { 70, 12}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_CONSTRUCTION_ENTRANCE, STR_RIDE_CONSTRUCTION_ENTRANCE_TIP ), MakeWidget ({109, 372}, { 70, 12}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_CONSTRUCTION_EXIT, STR_RIDE_CONSTRUCTION_EXIT_TIP ), MakeWidget ({ 94, 338}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_ROTATE_ARROW), STR_ROTATE_90_TIP ), MakeWidget ({ 41, 132}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_U_SHAPED_TRACK), STR_RIDE_CONSTRUCTION_U_SHAPED_OPEN_TRACK_TIP ), MakeWidget ({144, 132}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RIDE_CONSTRUCTION_O_SHAPED_TRACK), STR_RIDE_CONSTRUCTION_O_SHAPED_ENCLOSED_TRACK_TIP ), MakeWidget ({118, 120}, { 89, 41}, WindowWidgetType::Groupbox, WindowColour::Primary , STR_RIDE_CONSTRUCTION_SEAT_ROT ), MakeSpinnerWidgets({123, 138}, { 58, 12}, WindowWidgetType::Spinner, WindowColour::Secondary, 0, STR_RIDE_CONSTRUCTION_SELECT_SEAT_ROTATION_ANGLE_TIP), MakeWidget ({161, 338}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_G2_SIMULATE), STR_SIMULATE_RIDE_TIP ), kWidgetsEnd, }; // clang-format on #pragma endregion static bool _trackPlaceCtrlState; static int32_t _trackPlaceCtrlZ; static bool _trackPlaceShiftState; static ScreenCoordsXY _trackPlaceShiftStart; static int32_t _trackPlaceShiftZ; static int32_t _trackPlaceZ; static money64 _trackPlaceCost; static StringId _trackPlaceErrorMessage; static bool _autoRotatingShop; static bool _gotoStartPlacementMode = false; static constexpr StringId RideConstructionSeatAngleRotationStrings[] = { STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_NEG_180, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_NEG_135, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_NEG_90, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_NEG_45, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_0, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_45, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_90, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_135, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_180, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_225, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_270, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_315, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_360, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_405, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_450, STR_RIDE_CONSTRUCTION_SEAT_ROTATION_ANGLE_495, }; static void WindowRideConstructionMouseUpDemolishNextPiece(const CoordsXYZD& piecePos, int32_t type); /* move to ride.c */ static void CloseRideWindowForConstruction(RideId rideId) { WindowBase* w = WindowFindByNumber(WindowClass::Ride, rideId.ToUnderlying()); if (w != nullptr && w->page == 1) WindowClose(*w); } static void RideConstructPlacedForwardGameActionCallback(const GameAction* ga, const GameActions::Result* result); static void RideConstructPlacedBackwardGameActionCallback(const GameAction* ga, const GameActions::Result* result); static void CloseConstructWindowOnCompletion(const Ride& ride); class RideConstructionWindow final : public Window { private: uint8_t _currentlyShowingBrakeOrBoosterSpeed{}; SpecialElementsDropdownState _specialElementDropdownState; bool _autoOpeningShop{}; public: void OnOpen() override { auto currentRide = GetRide(_currentRideIndex); if (currentRide == nullptr) { return; } widgets = _rideConstructionWidgets; number = _currentRideIndex.ToUnderlying(); InitScrollWidgets(); WindowPushOthersRight(*this); ShowGridlines(); _currentTrackPrice = kMoney64Undefined; _currentBrakeSpeed2 = 8; _currentSeatRotationAngle = 4; _currentTrackCurve = currentRide->GetRideTypeDescriptor().StartTrackPiece | RideConstructionSpecialPieceSelected; _currentTrackPitchEnd = TrackPitch::None; _currentTrackRollEnd = TrackRoll::None; _currentTrackLiftHill = 0; _currentTrackAlternative = RIDE_TYPE_NO_ALTERNATIVES; if (currentRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_START_CONSTRUCTION_INVERTED)) _currentTrackAlternative |= RIDE_TYPE_ALTERNATIVE_TRACK_TYPE; _previousTrackRollEnd = TrackRoll::None; _previousTrackPitchEnd = TrackPitch::None; _currentTrackPieceDirection = 0; _rideConstructionState = RideConstructionState::Place; _currentTrackSelectionFlags = 0; _autoOpeningShop = false; _autoRotatingShop = true; _trackPlaceCtrlState = false; _trackPlaceShiftState = false; } void OnClose() override { RideConstructionInvalidateCurrentTrack(); ViewportSetVisibility(ViewportVisibility::Default); MapInvalidateMapSelectionTiles(); gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; // In order to cancel the yellow arrow correctly the // selection tool should be cancelled. Don't do a tool cancel if // another window has already taken control of tool. if (classification == gCurrentToolWidget.window_classification && number == gCurrentToolWidget.window_number) ToolCancel(); HideGridlines(); // If we demolish a currentRide all windows will be closed including the construction window, // the currentRide at this point is already gone. auto currentRide = GetRide(_currentRideIndex); if (currentRide == nullptr) { return; } if (RideTryGetOriginElement(*currentRide, nullptr)) { // Auto open shops if required. if (currentRide->mode == RideMode::ShopStall && gConfigGeneral.AutoOpenShops) { // HACK: Until we find a good a way to defer the game command for opening the shop, stop this // from getting stuck in an infinite loop as opening the currentRide will try to close this window if (!_autoOpeningShop) { _autoOpeningShop = true; auto gameAction = RideSetStatusAction(currentRide->id, RideStatus::Open); GameActions::Execute(&gameAction); _autoOpeningShop = false; } } currentRide->SetToDefaultInspectionInterval(); auto intent = Intent(WindowClass::Ride); intent.PutExtra(INTENT_EXTRA_RIDE_ID, currentRide->id.ToUnderlying()); ContextOpenIntent(&intent); } else { auto gameAction = RideDemolishAction(currentRide->id, RIDE_MODIFY_DEMOLISH); gameAction.SetFlags(GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED); GameActions::Execute(&gameAction); } } void OnResize() override { ResizeFrame(); WindowRideConstructionUpdateEnabledTrackPieces(); auto currentRide = GetRide(_currentRideIndex); if (currentRide == nullptr) { return; } const auto& rtd = currentRide->GetRideTypeDescriptor(); const auto currentTrackDrawerDescriptor = getCurrentTrackDrawerDescriptor(rtd); uint64_t disabledWidgets = 0; if (_currentTrackCurve & RideConstructionSpecialPieceSelected) { disabledWidgets |= (1uLL << WIDX_SLOPE_GROUPBOX) | (1uLL << WIDX_BANKING_GROUPBOX) | (1uLL << WIDX_SLOPE_DOWN_STEEP) | (1uLL << WIDX_SLOPE_DOWN) | (1uLL << WIDX_LEVEL) | (1uLL << WIDX_SLOPE_UP) | (1uLL << WIDX_SLOPE_UP_STEEP) | (1uLL << WIDX_CHAIN_LIFT) | (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_STRAIGHT) | (1uLL << WIDX_BANK_RIGHT); } // Disable large curves if the start or end of the track is sloped and large sloped curves are not available if ((_previousTrackPitchEnd != TrackPitch::None || _currentTrackPitchEnd != TrackPitch::None)) { if (!IsTrackEnabled(TRACK_SLOPE_CURVE_LARGE) || !(_previousTrackPitchEnd == TrackPitch::Up25 || _previousTrackPitchEnd == TrackPitch::Down25) || !(_currentTrackPitchEnd == TrackPitch::Up25 || _currentTrackPitchEnd == TrackPitch::Down25)) { disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_LARGE) | (1uLL << WIDX_RIGHT_CURVE_LARGE); } } if (IsTrackEnabled(TRACK_SLOPE_CURVE) && IsTrackEnabled(TRACK_CURVE_VERY_SMALL)) { // Disable small curves if the start or end of the track is sloped. if (_previousTrackPitchEnd != TrackPitch::None || _currentTrackPitchEnd != TrackPitch::None) { disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_VERY_SMALL) | (1uLL << WIDX_RIGHT_CURVE_VERY_SMALL); } } if (!IsTrackEnabled(TRACK_SLOPE_CURVE)) { if (IsTrackEnabled(TRACK_CURVE_VERTICAL)) { // Disable all curves only on vertical track if (_previousTrackPitchEnd != TrackPitch::Up90 || _currentTrackPitchEnd != TrackPitch::Up90) { if (_previousTrackPitchEnd != TrackPitch::Down90 || _currentTrackPitchEnd != TrackPitch::Down90) { disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_VERY_SMALL) | (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_SMALL) | (1uLL << WIDX_RIGHT_CURVE_VERY_SMALL); } } } else { // Disable all curves on sloped track if (_previousTrackPitchEnd != TrackPitch::None || _currentTrackPitchEnd != TrackPitch::None) { disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_VERY_SMALL) | (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_SMALL) | (1uLL << WIDX_RIGHT_CURVE_VERY_SMALL); } } } if (!IsTrackEnabled(TRACK_FLAT_ROLL_BANKING)) { // Disable banking disabledWidgets |= (1uLL << WIDX_BANKING_GROUPBOX) | (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_STRAIGHT) | (1uLL << WIDX_BANK_RIGHT); } // Disable banking if the start track is steep and the end of the track becomes flat. if ((_previousTrackPitchEnd == TrackPitch::Down60 || _previousTrackPitchEnd == TrackPitch::Up60) && _currentTrackPitchEnd == TrackPitch::None) { disabledWidgets |= (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_RIGHT); } if (!IsTrackEnabled(TRACK_SLOPE) && !IsTrackEnabled(TRACK_SLOPE_STEEP_DOWN) && !IsTrackEnabled(TRACK_SLOPE_STEEP_UP)) { if (!currentRide->GetRideTypeDescriptor().SupportsTrackPiece(TRACK_REVERSE_FREEFALL)) { // Disable all slopes disabledWidgets |= (1uLL << WIDX_SLOPE_GROUPBOX) | (1uLL << WIDX_SLOPE_DOWN_STEEP) | (1uLL << WIDX_SLOPE_DOWN) | (1uLL << WIDX_LEVEL) | (1uLL << WIDX_SLOPE_UP) | (1uLL << WIDX_SLOPE_UP_STEEP); } } // If ride type does not have access to diagonal sloped turns, disallow simultaneous use of banked and sloped // diagonals if (!IsTrackEnabled(TRACK_SLOPE_CURVE_LARGE) && TrackPieceDirectionIsDiagonal(_currentTrackPieceDirection)) { if (_currentTrackPitchEnd != TrackPitch::None) { disabledWidgets |= (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_RIGHT); } else if (_currentTrackRollEnd != TrackRoll::None) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN) | (1uLL << WIDX_SLOPE_UP); } } if (currentRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_UP_INCLINE_REQUIRES_LIFT) && !GetGameState().Cheats.EnableAllDrawableTrackPieces) { // Disable lift hill toggle and banking if current track piece is uphill if (_previousTrackPitchEnd == TrackPitch::Up25 || _previousTrackPitchEnd == TrackPitch::Up60 || _currentTrackPitchEnd == TrackPitch::Up25 || _currentTrackPitchEnd == TrackPitch::Up60) disabledWidgets |= 1uLL << WIDX_CHAIN_LIFT | (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_RIGHT); // Disable upward slope if current track piece is not flat if ((_previousTrackPitchEnd != TrackPitch::None || _previousTrackRollEnd != TrackRoll::None) && !(_currentTrackLiftHill & CONSTRUCTION_LIFT_HILL_SELECTED)) disabledWidgets |= (1uLL << WIDX_SLOPE_UP); } if (_rideConstructionState == RideConstructionState::State0) { disabledWidgets |= (1uLL << WIDX_CONSTRUCT) | (1uLL << WIDX_DEMOLISH) | (1uLL << WIDX_PREVIOUS_SECTION) | (1uLL << WIDX_NEXT_SECTION); } switch (_currentTrackCurve) { case EnumValue(TrackCurve::LeftVerySmall): case EnumValue(TrackCurve::LeftSmall): case EnumValue(TrackCurve::Left): case EnumValue(TrackCurve::LeftLarge): disabledWidgets |= (1uLL << WIDX_BANK_RIGHT); if (_previousTrackRollEnd == TrackRoll::None) { disabledWidgets |= (1uLL << WIDX_BANK_LEFT); } else { disabledWidgets |= (1uLL << WIDX_BANK_STRAIGHT); } break; case EnumValue(TrackCurve::RightLarge): case EnumValue(TrackCurve::Right): case EnumValue(TrackCurve::RightSmall): case EnumValue(TrackCurve::RightVerySmall): disabledWidgets |= (1uLL << WIDX_BANK_LEFT); if (_previousTrackRollEnd == TrackRoll::None) { disabledWidgets |= (1uLL << WIDX_BANK_RIGHT); } else { disabledWidgets |= (1uLL << WIDX_BANK_STRAIGHT); } break; } if (!IsTrackEnabled(TRACK_SLOPE_ROLL_BANKING)) { if (_currentTrackRollEnd != TrackRoll::None) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN) | (1uLL << WIDX_SLOPE_UP); } } if (_previousTrackPitchEnd == _currentTrackPitchEnd) { switch (_currentTrackPitchEnd) { case TrackPitch::Up60: case TrackPitch::Down60: disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_VERY_SMALL) | (1uLL << WIDX_LEFT_CURVE) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_VERY_SMALL); if (!IsTrackEnabled(TRACK_SLOPE_CURVE_STEEP)) { disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_RIGHT_CURVE_SMALL); } break; case TrackPitch::Up90: case TrackPitch::Down90: disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_VERY_SMALL) | (1uLL << WIDX_LEFT_CURVE) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_VERY_SMALL); if (!IsTrackEnabled(TRACK_CURVE_VERTICAL)) { disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_RIGHT_CURVE_SMALL); } break; default: break; } } else { // Disable all curves disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_VERY_SMALL) | (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_SMALL) | (1uLL << WIDX_RIGHT_CURVE_VERY_SMALL); } switch (_previousTrackPitchEnd) { case TrackPitch::None: if (_currentTrackCurve != EnumValue(TrackCurve::None) || (IsTrackEnabled(TRACK_SLOPE_STEEP_LONG) && TrackPieceDirectionIsDiagonal(_currentTrackPieceDirection))) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN_STEEP) | (1uLL << WIDX_SLOPE_UP_STEEP); } break; case TrackPitch::Down25: disabledWidgets |= (1uLL << WIDX_SLOPE_UP) | (1uLL << WIDX_SLOPE_UP_STEEP); break; case TrackPitch::Down60: disabledWidgets |= (1uLL << WIDX_SLOPE_UP) | (1uLL << WIDX_SLOPE_UP_STEEP); if (!IsTrackEnabled(TRACK_SLOPE_LONG) && !( IsTrackEnabled(TRACK_SLOPE_STEEP_LONG) && !TrackPieceDirectionIsDiagonal(_currentTrackPieceDirection))) { disabledWidgets |= (1uLL << WIDX_LEVEL); } break; case TrackPitch::Up25: disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN_STEEP) | (1uLL << WIDX_SLOPE_DOWN); break; case TrackPitch::Up60: disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN_STEEP) | (1uLL << WIDX_SLOPE_DOWN); if (!IsTrackEnabled(TRACK_SLOPE_LONG) && !( IsTrackEnabled(TRACK_SLOPE_STEEP_LONG) && !TrackPieceDirectionIsDiagonal(_currentTrackPieceDirection))) { disabledWidgets |= (1uLL << WIDX_LEVEL); } break; case TrackPitch::Down90: case TrackPitch::Up90: disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN) | (1uLL << WIDX_LEVEL) | (1uLL << WIDX_SLOPE_UP); break; } if (_previousTrackPitchEnd == TrackPitch::None) { if (!IsTrackEnabled(TRACK_SLOPE_LONG) && !IsTrackEnabled(TRACK_SLOPE_STEEP_LONG)) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN_STEEP) | (1uLL << WIDX_SLOPE_UP_STEEP); } } if (IsTrackEnabled(TRACK_SLOPE_VERTICAL)) { if (_previousTrackPitchEnd == TrackPitch::Up60 && _currentTrackPieceDirection < 4) { disabledWidgets &= ~(1uLL << WIDX_SLOPE_DOWN_STEEP); } if (_previousTrackPitchEnd == TrackPitch::Up90) { disabledWidgets &= ~(1uLL << WIDX_SLOPE_DOWN_STEEP); } if (_previousTrackPitchEnd == TrackPitch::Down60 && _currentTrackPieceDirection < 4) { disabledWidgets &= ~(1uLL << WIDX_SLOPE_UP_STEEP); } } if (_previousTrackRollEnd == TrackRoll::Left) { disabledWidgets |= (1uLL << WIDX_RIGHT_CURVE_SMALL) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_LARGE) | (1uLL << WIDX_BANK_RIGHT); } if (_previousTrackRollEnd == TrackRoll::Right) { disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE) | (1uLL << WIDX_LEFT_CURVE_LARGE) | (1uLL << WIDX_BANK_LEFT); } if (_currentTrackRollEnd != _previousTrackRollEnd) { disabledWidgets |= (1uLL << WIDX_RIGHT_CURVE_SMALL) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_LARGE) | (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE) | (1uLL << WIDX_LEFT_CURVE_LARGE); } if (_currentTrackPitchEnd != TrackPitch::None) { if (IsTrackEnabled(TRACK_SLOPE_ROLL_BANKING)) { if (_previousTrackPitchEnd == TrackPitch::None) { if (_currentTrackPitchEnd != TrackPitch::Up25 && _currentTrackPitchEnd != TrackPitch::Down25) { disabledWidgets |= (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_RIGHT); } } else { if (_currentTrackPitchEnd != _previousTrackPitchEnd) { disabledWidgets |= (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_RIGHT); } else { if (_currentTrackPitchEnd != TrackPitch::Up25 && _currentTrackPitchEnd != TrackPitch::Down25) { disabledWidgets |= (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_RIGHT); } } } } else { disabledWidgets |= (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_RIGHT); } } if (_currentTrackRollEnd != TrackRoll::None || _previousTrackRollEnd != TrackRoll::None) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN_STEEP) | (1uLL << WIDX_SLOPE_UP_STEEP) | (1uLL << WIDX_CHAIN_LIFT); } if (_currentTrackCurve != EnumValue(TrackCurve::None)) { if (!IsTrackEnabled(TRACK_LIFT_HILL_CURVE)) { disabledWidgets |= (1uLL << WIDX_CHAIN_LIFT); } if (_currentTrackPitchEnd == TrackPitch::None) { disabledWidgets |= (1uLL << WIDX_CHAIN_LIFT); } if (_currentTrackPitchEnd == TrackPitch::Up60) { disabledWidgets |= (1uLL << WIDX_CHAIN_LIFT); } if (_currentTrackPitchEnd == TrackPitch::Down60) { disabledWidgets |= (1uLL << WIDX_CHAIN_LIFT); } } if (_currentTrackPitchEnd == TrackPitch::Up90 || _previousTrackPitchEnd == TrackPitch::Up90) { disabledWidgets |= (1uLL << WIDX_CHAIN_LIFT); } if (!IsTrackEnabled(TRACK_LIFT_HILL_STEEP)) { if (_previousTrackPitchEnd == TrackPitch::Up60 || _currentTrackPitchEnd == TrackPitch::Up60) { disabledWidgets |= (1uLL << WIDX_CHAIN_LIFT); } } if (_previousTrackRollEnd == TrackRoll::UpsideDown) { disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE) | (1uLL << WIDX_LEFT_CURVE_LARGE) | (1uLL << WIDX_STRAIGHT) | (1uLL << WIDX_RIGHT_CURVE_SMALL) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_LARGE); } if (_currentTrackCurve != EnumValue(TrackCurve::None)) { if (_currentTrackPitchEnd == TrackPitch::None) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN) | (1uLL << WIDX_SLOPE_UP); } if (_currentTrackPitchEnd == _previousTrackPitchEnd) { if (_currentTrackPitchEnd == TrackPitch::Up25) { disabledWidgets |= (1uLL << WIDX_SLOPE_UP_STEEP); if (_currentTrackCurve == EnumValue(TrackCurve::Left) || _currentTrackCurve == EnumValue(TrackCurve::Right) || _rideConstructionState != RideConstructionState::Back || !IsTrackEnabled(TRACK_SLOPE_CURVE_BANKED)) { disabledWidgets |= (1uLL << WIDX_LEVEL); } } if (_currentTrackPitchEnd == TrackPitch::Down25) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN_STEEP); if (_currentTrackCurve == EnumValue(TrackCurve::Left) || _currentTrackCurve == EnumValue(TrackCurve::Right) || _rideConstructionState != RideConstructionState::Front || !IsTrackEnabled(TRACK_SLOPE_CURVE_BANKED)) { disabledWidgets |= (1uLL << WIDX_LEVEL); } } } else if (IsTrackEnabled(TRACK_SLOPE_CURVE_BANKED)) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN_STEEP) | (1uLL << WIDX_SLOPE_UP_STEEP); if (_currentTrackRollEnd == TrackRoll::Left) { disabledWidgets |= (1uLL << WIDX_BANK_STRAIGHT) | (1uLL << WIDX_BANK_RIGHT); disabledWidgets &= ~(1uLL << WIDX_BANK_LEFT); } if (_currentTrackRollEnd == TrackRoll::Right) { disabledWidgets |= (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_STRAIGHT); disabledWidgets &= ~(1uLL << WIDX_BANK_RIGHT); } if (_currentTrackRollEnd == TrackRoll::None) { disabledWidgets |= (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_RIGHT); disabledWidgets &= ~(1uLL << WIDX_BANK_STRAIGHT); } if (_currentTrackPitchEnd == TrackPitch::None) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN) | (1uLL << WIDX_SLOPE_UP); disabledWidgets &= ~(1uLL << WIDX_LEVEL); } if (_currentTrackPitchEnd == TrackPitch::Up25) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN) | (1uLL << WIDX_LEVEL); disabledWidgets &= ~(1uLL << WIDX_SLOPE_UP); } if (_currentTrackPitchEnd == TrackPitch::Down25) { disabledWidgets |= (1uLL << WIDX_LEVEL) | (1uLL << WIDX_SLOPE_UP); disabledWidgets &= ~(1uLL << WIDX_SLOPE_DOWN); } if (_currentTrackCurve == EnumValue(TrackCurve::LeftSmall)) { disabledWidgets &= ~(1uLL << WIDX_LEFT_CURVE_SMALL); } if (_currentTrackCurve == EnumValue(TrackCurve::RightSmall)) { disabledWidgets &= ~(1uLL << WIDX_RIGHT_CURVE_SMALL); } } } if (_currentTrackCurve != EnumValue(TrackCurve::None) && _currentTrackPitchEnd == TrackPitch::Up60) { disabledWidgets |= (1uLL << WIDX_SLOPE_UP); } if (_currentTrackCurve != EnumValue(TrackCurve::None) && _currentTrackPitchEnd == TrackPitch::Down60) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN); } if ((_currentTrackLiftHill & CONSTRUCTION_LIFT_HILL_SELECTED) && !GetGameState().Cheats.EnableChainLiftOnAllTrack) { if (_currentTrackPitchEnd != TrackPitch::None && !IsTrackEnabled(TRACK_LIFT_HILL_CURVE)) { disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE) | (1uLL << WIDX_LEFT_CURVE_LARGE) | (1uLL << WIDX_RIGHT_CURVE_SMALL) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_LARGE); } if (!IsTrackEnabled(TRACK_LIFT_HILL_STEEP)) { if (widgets[WIDX_SLOPE_UP_STEEP].tooltip == STR_RIDE_CONSTRUCTION_STEEP_SLOPE_UP_TIP) { disabledWidgets |= (1uLL << WIDX_SLOPE_UP_STEEP); } } } if (_previousTrackPitchEnd == TrackPitch::Up60 && _currentTrackCurve != EnumValue(TrackCurve::None)) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN_STEEP) | (1uLL << WIDX_LEVEL); } if (_previousTrackPitchEnd == TrackPitch::Down60 && _currentTrackCurve != EnumValue(TrackCurve::None)) { disabledWidgets |= (1uLL << WIDX_LEVEL) | (1uLL << WIDX_SLOPE_UP_STEEP); } if (_currentTrackPitchEnd == TrackPitch::Up90 || _previousTrackPitchEnd == TrackPitch::Up90) { if (_currentTrackCurve != EnumValue(TrackCurve::None)) { disabledWidgets |= (1uLL << WIDX_SLOPE_UP_STEEP); } disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_LARGE) | (1uLL << WIDX_RIGHT_CURVE_LARGE); if (currentRide->GetRideTypeDescriptor().SupportsTrackPiece(TRACK_REVERSE_FREEFALL)) { disabledWidgets |= (1uLL << WIDX_STRAIGHT) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE); } } else if (_currentTrackPitchEnd == TrackPitch::Down90 || _previousTrackPitchEnd == TrackPitch::Down90) { if (_currentTrackCurve != EnumValue(TrackCurve::None)) { disabledWidgets |= (1uLL << WIDX_SLOPE_DOWN_STEEP); } disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_LARGE) | (1uLL << WIDX_RIGHT_CURVE_LARGE); if (currentRide->GetRideTypeDescriptor().SupportsTrackPiece(TRACK_REVERSE_FREEFALL)) { disabledWidgets |= (1uLL << WIDX_STRAIGHT) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE); } } // If the previous track is flat and the next track is flat, attempt to show buttons for helixes if (_currentTrackPitchEnd == TrackPitch::None && _currentTrackPitchEnd == _previousTrackPitchEnd) { // If the bank is none, attempt to show unbanked quarter helixes if (_currentTrackRollEnd == TrackRoll::None && (_currentTrackCurve == EnumValue(TrackCurve::Left) || _currentTrackCurve == EnumValue(TrackCurve::Right))) { if (IsTrackEnabled(TRACK_HELIX_DOWN_UNBANKED_QUARTER)) disabledWidgets &= ~(1uLL << WIDX_SLOPE_DOWN_STEEP); if (IsTrackEnabled(TRACK_HELIX_UP_UNBANKED_QUARTER)) disabledWidgets &= ~(1uLL << WIDX_SLOPE_UP_STEEP); } // If the track is banked left or right and curvature is standard size (2.5 tile radius), attempt to show // buttons for half or quarter helixes else if ( (_currentTrackRollEnd == TrackRoll::Left || _currentTrackRollEnd == TrackRoll::Right) && (_currentTrackCurve == EnumValue(TrackCurve::Left) || _currentTrackCurve == EnumValue(TrackCurve::Right))) { if (IsTrackEnabled(TRACK_HELIX_DOWN_BANKED_HALF) || IsTrackEnabled(TRACK_HELIX_DOWN_BANKED_QUARTER)) disabledWidgets &= ~(1uLL << WIDX_SLOPE_DOWN_STEEP); if (IsTrackEnabled(TRACK_HELIX_UP_BANKED_HALF) || IsTrackEnabled(TRACK_HELIX_UP_BANKED_QUARTER)) disabledWidgets &= ~(1uLL << WIDX_SLOPE_UP_STEEP); } // If the track is banked left or right and curvature is small size (1.5 tile radius), attempt to show buttons // for half helixes else if ( (_currentTrackRollEnd == TrackRoll::Left || _currentTrackRollEnd == TrackRoll::Right) && (_currentTrackCurve == EnumValue(TrackCurve::LeftSmall) || _currentTrackCurve == EnumValue(TrackCurve::RightSmall))) { if (IsTrackEnabled(TRACK_HELIX_DOWN_BANKED_HALF)) disabledWidgets &= ~(1uLL << WIDX_SLOPE_DOWN_STEEP); if (IsTrackEnabled(TRACK_HELIX_UP_BANKED_HALF)) disabledWidgets &= ~(1uLL << WIDX_SLOPE_UP_STEEP); } } if (IsTrackEnabled(TRACK_SLOPE_CURVE_BANKED)) { if (_rideConstructionState == RideConstructionState::Front) { if (_currentTrackCurve == EnumValue(TrackCurve::LeftSmall) || _currentTrackCurve == EnumValue(TrackCurve::RightSmall)) { if (_currentTrackPitchEnd == TrackPitch::None && _previousTrackRollEnd != TrackRoll::None && (!currentRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_UP_INCLINE_REQUIRES_LIFT) || GetGameState().Cheats.EnableAllDrawableTrackPieces)) { disabledWidgets &= ~(1uLL << WIDX_SLOPE_UP); } } } else if (_rideConstructionState == RideConstructionState::Back) { if (_currentTrackCurve == EnumValue(TrackCurve::LeftSmall) || _currentTrackCurve == EnumValue(TrackCurve::RightSmall)) { if (_currentTrackPitchEnd == TrackPitch::None && _previousTrackRollEnd != TrackRoll::None) { disabledWidgets &= ~(1uLL << WIDX_SLOPE_DOWN); } } } } if (_currentTrackPieceDirection >= 4) { disabledWidgets |= (1uLL << WIDX_LEFT_CURVE_VERY_SMALL) | (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_SMALL) | (1uLL << WIDX_RIGHT_CURVE_VERY_SMALL); } if (_rideConstructionState == RideConstructionState::Front) { disabledWidgets |= (1uLL << WIDX_NEXT_SECTION); if (WindowRideConstructionUpdateState(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { disabledWidgets |= (1uLL << WIDX_CONSTRUCT); } } else if (_rideConstructionState == RideConstructionState::Back) { disabledWidgets |= (1uLL << WIDX_PREVIOUS_SECTION); if (WindowRideConstructionUpdateState(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)) { disabledWidgets |= (1uLL << WIDX_CONSTRUCT); } } if (currentTrackDrawerDescriptor.HasCoveredPieces()) { disabledWidgets &= ~(1uLL << WIDX_BANKING_GROUPBOX); } if (_rideConstructionState == RideConstructionState::EntranceExit || _rideConstructionState == RideConstructionState::Selected) { disabledWidgets |= (1uLL << WIDX_DIRECTION_GROUPBOX) | (1uLL << WIDX_SLOPE_GROUPBOX) | (1uLL << WIDX_BANKING_GROUPBOX) | (1uLL << WIDX_LEFT_CURVE_VERY_SMALL) | (1uLL << WIDX_LEFT_CURVE_SMALL) | (1uLL << WIDX_LEFT_CURVE) | (1uLL << WIDX_STRAIGHT) | (1uLL << WIDX_RIGHT_CURVE) | (1uLL << WIDX_RIGHT_CURVE_SMALL) | (1uLL << WIDX_RIGHT_CURVE_VERY_SMALL) | (1uLL << WIDX_SPECIAL_TRACK_DROPDOWN) | (1uLL << WIDX_SLOPE_DOWN_STEEP) | (1uLL << WIDX_SLOPE_DOWN) | (1uLL << WIDX_LEVEL) | (1uLL << WIDX_SLOPE_UP) | (1uLL << WIDX_SLOPE_UP_STEEP) | (1uLL << WIDX_CHAIN_LIFT) | (1uLL << WIDX_BANK_LEFT) | (1uLL << WIDX_BANK_STRAIGHT) | (1uLL << WIDX_BANK_RIGHT) | (1uLL << WIDX_LEFT_CURVE_LARGE) | (1uLL << WIDX_RIGHT_CURVE_LARGE); } if (_currentlyShowingBrakeOrBoosterSpeed) { disabledWidgets &= ~(1uLL << WIDX_BANKING_GROUPBOX); disabledWidgets &= ~(1uLL << WIDX_BANK_LEFT); disabledWidgets &= ~(1uLL << WIDX_BANK_STRAIGHT); disabledWidgets &= ~(1uLL << WIDX_BANK_RIGHT); } // If chain lift cheat is enabled then show the chain lift widget no matter what if (GetGameState().Cheats.EnableChainLiftOnAllTrack) { disabledWidgets &= ~(1uLL << WIDX_CHAIN_LIFT); } // Set and invalidate the changed widgets uint64_t currentDisabledWidgets = disabled_widgets; if (currentDisabledWidgets == disabledWidgets) return; for (WidgetIndex i = 0; i < 64; i++) { if ((disabledWidgets & (1uLL << i)) != (currentDisabledWidgets & (1uLL << i))) { WidgetInvalidate(*this, i); } } disabled_widgets = disabledWidgets; } void OnUpdate() override { auto currentRide = GetRide(_currentRideIndex); if (currentRide == nullptr) { return; } // Close construction window if currentRide is not closed, // editing currentRide while open will cause many issues until properly handled if (currentRide->status != RideStatus::Closed && currentRide->status != RideStatus::Simulating) { Close(); return; } switch (_currentTrackCurve) { case TrackElemType::SpinningTunnel | RideConstructionSpecialPieceSelected: case TrackElemType::Whirlpool | RideConstructionSpecialPieceSelected: case TrackElemType::Rapids | RideConstructionSpecialPieceSelected: case TrackElemType::Waterfall | RideConstructionSpecialPieceSelected: WidgetInvalidate(*this, WIDX_CONSTRUCT); break; } if (_rideConstructionState == RideConstructionState::Place) { if (!WidgetIsActiveTool(*this, WIDX_CONSTRUCT)) { Close(); return; } } if (_rideConstructionState == RideConstructionState::EntranceExit) { if (!WidgetIsActiveTool(*this, WIDX_ENTRANCE) && !WidgetIsActiveTool(*this, WIDX_EXIT)) { _rideConstructionState = gRideEntranceExitPlacePreviousRideConstructionState; WindowRideConstructionUpdateActiveElements(); } } switch (_rideConstructionState) { case RideConstructionState::Front: case RideConstructionState::Back: case RideConstructionState::Selected: if ((InputTestFlag(INPUT_FLAG_TOOL_ACTIVE)) && gCurrentToolWidget.window_classification == WindowClass::RideConstruction) { ToolCancel(); } break; default: break; } UpdateGhostTrackAndArrow(); } void OnMouseUp(WidgetIndex widgetIndex) override { WindowRideConstructionUpdateEnabledTrackPieces(); switch (widgetIndex) { case WIDX_CLOSE: Close(); break; case WIDX_NEXT_SECTION: RideSelectNextSection(); break; case WIDX_PREVIOUS_SECTION: RideSelectPreviousSection(); break; case WIDX_CONSTRUCT: Construct(); // Force any footpath construction to recheck the area. gProvisionalFootpath.Flags |= PROVISIONAL_PATH_FLAG_2; break; case WIDX_DEMOLISH: MouseUpDemolish(); break; case WIDX_ROTATE: Rotate(); break; case WIDX_ENTRANCE: EntranceClick(); break; case WIDX_EXIT: ExitClick(); break; case WIDX_SIMULATE: { auto currentRide = GetRide(_currentRideIndex); if (currentRide != nullptr) { auto status = currentRide->status == RideStatus::Simulating ? RideStatus::Closed : RideStatus::Simulating; auto gameAction = RideSetStatusAction(currentRide->id, status); GameActions::Execute(&gameAction); } break; } } } void OnMouseDown(WidgetIndex widgetIndex) override { auto currentRide = GetRide(_currentRideIndex); if (currentRide == nullptr) { return; } WindowRideConstructionUpdateEnabledTrackPieces(); switch (widgetIndex) { case WIDX_LEFT_CURVE: RideConstructionInvalidateCurrentTrack(); _currentTrackCurve = EnumValue(TrackCurve::Left); _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_RIGHT_CURVE: RideConstructionInvalidateCurrentTrack(); _currentTrackCurve = EnumValue(TrackCurve::Right); _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_LEFT_CURVE_SMALL: RideConstructionInvalidateCurrentTrack(); _currentTrackCurve = EnumValue(TrackCurve::LeftSmall); _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_RIGHT_CURVE_SMALL: RideConstructionInvalidateCurrentTrack(); _currentTrackCurve = EnumValue(TrackCurve::RightSmall); _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_LEFT_CURVE_VERY_SMALL: RideConstructionInvalidateCurrentTrack(); _currentTrackCurve = EnumValue(TrackCurve::LeftVerySmall); _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_RIGHT_CURVE_VERY_SMALL: RideConstructionInvalidateCurrentTrack(); _currentTrackCurve = EnumValue(TrackCurve::RightVerySmall); _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_LEFT_CURVE_LARGE: RideConstructionInvalidateCurrentTrack(); _currentTrackCurve = EnumValue(TrackCurve::LeftLarge); _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_RIGHT_CURVE_LARGE: RideConstructionInvalidateCurrentTrack(); _currentTrackCurve = EnumValue(TrackCurve::RightLarge); _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_STRAIGHT: RideConstructionInvalidateCurrentTrack(); if (_currentTrackCurve != EnumValue(TrackCurve::None)) _currentTrackRollEnd = TrackRoll::None; _currentTrackCurve = EnumValue(TrackCurve::None); _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_SLOPE_DOWN_STEEP: RideConstructionInvalidateCurrentTrack(); if (IsTrackEnabled(TRACK_HELIX_DOWN_BANKED_HALF) || IsTrackEnabled(TRACK_HELIX_UP_BANKED_HALF)) { if (_currentTrackCurve == EnumValue(TrackCurve::Left) && _currentTrackRollEnd == TrackRoll::Left) { _currentTrackCurve = TrackElemType::LeftHalfBankedHelixDownLarge | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } if (_currentTrackCurve == EnumValue(TrackCurve::Right) && _currentTrackRollEnd == TrackRoll::Right) { _currentTrackCurve = TrackElemType::RightHalfBankedHelixDownLarge | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } if (_currentTrackCurve == EnumValue(TrackCurve::LeftSmall) && _currentTrackRollEnd == TrackRoll::Left) { _currentTrackCurve = TrackElemType::LeftHalfBankedHelixDownSmall | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } if (_currentTrackCurve == EnumValue(TrackCurve::RightSmall) && _currentTrackRollEnd == TrackRoll::Right) { _currentTrackCurve = TrackElemType::RightHalfBankedHelixDownSmall | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } } if (IsTrackEnabled(TRACK_HELIX_DOWN_BANKED_QUARTER) || IsTrackEnabled(TRACK_HELIX_UP_BANKED_QUARTER)) { if (_currentTrackCurve == EnumValue(TrackCurve::Left) && _currentTrackRollEnd == TrackRoll::Left) { _currentTrackCurve = TrackElemType::LeftQuarterBankedHelixLargeDown | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } if (_currentTrackCurve == EnumValue(TrackCurve::Right) && _currentTrackRollEnd == TrackRoll::Right) { _currentTrackCurve = TrackElemType::RightQuarterBankedHelixLargeDown | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } } if (IsTrackEnabled(TRACK_HELIX_DOWN_UNBANKED_QUARTER) || IsTrackEnabled(TRACK_HELIX_UP_UNBANKED_QUARTER)) { if (_currentTrackRollEnd == TrackRoll::None) { if (_currentTrackCurve == EnumValue(TrackCurve::Left)) { _currentTrackCurve = TrackElemType::LeftQuarterHelixLargeDown | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } if (_currentTrackCurve == EnumValue(TrackCurve::Right)) { _currentTrackCurve = TrackElemType::RightQuarterHelixLargeDown | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } } } if (widgets[WIDX_SLOPE_DOWN_STEEP].tooltip == STR_RIDE_CONSTRUCTION_STEEP_SLOPE_DOWN_TIP) { UpdateLiftHillSelected(TrackPitch::Down60); } else { UpdateLiftHillSelected(TrackPitch::Up90); } break; case WIDX_SLOPE_DOWN: RideConstructionInvalidateCurrentTrack(); if (_rideConstructionState == RideConstructionState::Back && _currentTrackRollEnd != TrackRoll::None) { _currentTrackRollEnd = TrackRoll::None; } UpdateLiftHillSelected(TrackPitch::Down25); break; case WIDX_LEVEL: RideConstructionInvalidateCurrentTrack(); if (_rideConstructionState == RideConstructionState::Front && _previousTrackPitchEnd == TrackPitch::Down25) { if (_currentTrackCurve == EnumValue(TrackCurve::LeftSmall)) { _currentTrackRollEnd = TrackRoll::Left; } else if (_currentTrackCurve == EnumValue(TrackCurve::RightSmall)) { _currentTrackRollEnd = TrackRoll::Right; } } else if ( _rideConstructionState == RideConstructionState::Back && _previousTrackPitchEnd == TrackPitch::Up25) { if (_currentTrackCurve == EnumValue(TrackCurve::LeftSmall)) { _currentTrackRollEnd = TrackRoll::Left; } else if (_currentTrackCurve == EnumValue(TrackCurve::RightSmall)) { _currentTrackRollEnd = TrackRoll::Right; } } UpdateLiftHillSelected(TrackPitch::None); break; case WIDX_SLOPE_UP: RideConstructionInvalidateCurrentTrack(); if (_rideConstructionState == RideConstructionState::Front && _currentTrackRollEnd != TrackRoll::None) { _currentTrackRollEnd = TrackRoll::None; } if (currentRide->GetRideTypeDescriptor().SupportsTrackPiece(TRACK_REVERSE_FREEFALL)) { if (_rideConstructionState == RideConstructionState::Front && _currentTrackCurve == EnumValue(TrackCurve::None)) { _currentTrackCurve = TrackElemType::ReverseFreefallSlope | RideConstructionSpecialPieceSelected; WindowRideConstructionUpdateActiveElements(); } } else { UpdateLiftHillSelected(TrackPitch::Up25); } break; case WIDX_SLOPE_UP_STEEP: RideConstructionInvalidateCurrentTrack(); if (IsTrackEnabled(TRACK_HELIX_DOWN_BANKED_HALF) || IsTrackEnabled(TRACK_HELIX_UP_BANKED_HALF)) { if (_currentTrackCurve == EnumValue(TrackCurve::Left) && _currentTrackRollEnd == TrackRoll::Left) { _currentTrackCurve = TrackElemType::LeftHalfBankedHelixUpLarge | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } if (_currentTrackCurve == EnumValue(TrackCurve::Right) && _currentTrackRollEnd == TrackRoll::Right) { _currentTrackCurve = TrackElemType::RightHalfBankedHelixUpLarge | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } if (_currentTrackCurve == EnumValue(TrackCurve::LeftSmall) && _currentTrackRollEnd == TrackRoll::Left) { _currentTrackCurve = TrackElemType::LeftHalfBankedHelixUpSmall | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } if (_currentTrackCurve == EnumValue(TrackCurve::RightSmall) && _currentTrackRollEnd == TrackRoll::Right) { _currentTrackCurve = TrackElemType::RightHalfBankedHelixUpSmall | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } } if (IsTrackEnabled(TRACK_HELIX_DOWN_BANKED_QUARTER) || IsTrackEnabled(TRACK_HELIX_UP_BANKED_QUARTER)) { if (_currentTrackCurve == EnumValue(TrackCurve::Left) && _currentTrackRollEnd == TrackRoll::Left) { _currentTrackCurve = TrackElemType::LeftQuarterBankedHelixLargeUp | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } if (_currentTrackCurve == EnumValue(TrackCurve::Right) && _currentTrackRollEnd == TrackRoll::Right) { _currentTrackCurve = TrackElemType::RightQuarterBankedHelixLargeUp | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } } if (IsTrackEnabled(TRACK_HELIX_DOWN_UNBANKED_QUARTER) || IsTrackEnabled(TRACK_HELIX_UP_UNBANKED_QUARTER)) { if (_currentTrackRollEnd == TrackRoll::None) { if (_currentTrackCurve == EnumValue(TrackCurve::Left)) { _currentTrackCurve = TrackElemType::LeftQuarterHelixLargeUp | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } if (_currentTrackCurve == EnumValue(TrackCurve::Right)) { _currentTrackCurve = TrackElemType::RightQuarterHelixLargeUp | RideConstructionSpecialPieceSelected; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; } } } if (widgets[WIDX_SLOPE_UP_STEEP].tooltip == STR_RIDE_CONSTRUCTION_STEEP_SLOPE_UP_TIP) { UpdateLiftHillSelected(TrackPitch::Up60); } else { UpdateLiftHillSelected(TrackPitch::Down90); } break; case WIDX_CHAIN_LIFT: RideConstructionInvalidateCurrentTrack(); _currentTrackLiftHill ^= CONSTRUCTION_LIFT_HILL_SELECTED; if ((_currentTrackLiftHill & CONSTRUCTION_LIFT_HILL_SELECTED) && !GetGameState().Cheats.EnableChainLiftOnAllTrack) _currentTrackAlternative &= ~RIDE_TYPE_ALTERNATIVE_TRACK_PIECES; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_BANK_LEFT: RideConstructionInvalidateCurrentTrack(); if (!_currentlyShowingBrakeOrBoosterSpeed) { _currentTrackRollEnd = TrackRoll::Left; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); } break; case WIDX_BANK_STRAIGHT: RideConstructionInvalidateCurrentTrack(); if (!_currentlyShowingBrakeOrBoosterSpeed) { _currentTrackRollEnd = TrackRoll::None; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); } else { uint8_t* brakesSpeedPtr = &_currentBrakeSpeed2; uint8_t maxBrakesSpeed = 30; uint8_t brakesSpeed = *brakesSpeedPtr + 2; if (brakesSpeed <= maxBrakesSpeed) { if (_rideConstructionState == RideConstructionState::Selected) { SetBrakeSpeed(brakesSpeed); } else { *brakesSpeedPtr = brakesSpeed; WindowRideConstructionUpdateActiveElements(); } } } break; case WIDX_BANK_RIGHT: RideConstructionInvalidateCurrentTrack(); if (!_currentlyShowingBrakeOrBoosterSpeed) { _currentTrackRollEnd = TrackRoll::Right; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); } else { uint8_t* brakesSpeedPtr = &_currentBrakeSpeed2; uint8_t brakesSpeed = *brakesSpeedPtr - 2; if (brakesSpeed >= 2) { if (_rideConstructionState == RideConstructionState::Selected) { SetBrakeSpeed(brakesSpeed); } else { *brakesSpeedPtr = brakesSpeed; WindowRideConstructionUpdateActiveElements(); } } } break; case WIDX_SPECIAL_TRACK_DROPDOWN: ShowSpecialTrackDropdown(&widgets[widgetIndex]); break; case WIDX_U_TRACK: RideConstructionInvalidateCurrentTrack(); _currentTrackAlternative &= ~RIDE_TYPE_ALTERNATIVE_TRACK_PIECES; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_O_TRACK: RideConstructionInvalidateCurrentTrack(); _currentTrackAlternative |= RIDE_TYPE_ALTERNATIVE_TRACK_PIECES; if (!GetGameState().Cheats.EnableChainLiftOnAllTrack) _currentTrackLiftHill &= ~CONSTRUCTION_LIFT_HILL_SELECTED; _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); break; case WIDX_SEAT_ROTATION_ANGLE_SPINNER_UP: if (_currentSeatRotationAngle < 15) { if (_rideConstructionState == RideConstructionState::Selected) { RideSelectedTrackSetSeatRotation(_currentSeatRotationAngle + 1); } else { _currentSeatRotationAngle++; WindowRideConstructionUpdateActiveElements(); } } break; case WIDX_SEAT_ROTATION_ANGLE_SPINNER_DOWN: if (_currentSeatRotationAngle > 0) { if (_rideConstructionState == RideConstructionState::Selected) { RideSelectedTrackSetSeatRotation(_currentSeatRotationAngle - 1); } else { _currentSeatRotationAngle--; WindowRideConstructionUpdateActiveElements(); } } break; } } void OnDropdown(WidgetIndex widgetIndex, int32_t selectedIndex) override { if (widgetIndex != WIDX_SPECIAL_TRACK_DROPDOWN) return; if (selectedIndex == -1) return; RideConstructionInvalidateCurrentTrack(); _currentTrackPrice = kMoney64Undefined; track_type_t trackPiece = _specialElementDropdownState.Elements[selectedIndex].TrackType; switch (trackPiece) { case TrackElemType::EndStation: case TrackElemType::SBendLeft: case TrackElemType::SBendRight: _currentTrackPitchEnd = TrackPitch::None; break; case TrackElemType::LeftVerticalLoop: case TrackElemType::RightVerticalLoop: _currentTrackRollEnd = TrackRoll::None; _currentTrackLiftHill &= ~CONSTRUCTION_LIFT_HILL_SELECTED; break; case TrackElemType::BlockBrakes: case TrackElemType::DiagBlockBrakes: _currentBrakeSpeed2 = kRCT2DefaultBlockBrakeSpeed; } _currentTrackCurve = trackPiece | RideConstructionSpecialPieceSelected; WindowRideConstructionUpdateActiveElements(); } void OnToolUpdate(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override { switch (widgetIndex) { case WIDX_CONSTRUCT: RideConstructionToolupdateConstruct(screenCoords); break; case WIDX_ENTRANCE: case WIDX_EXIT: RideConstructionToolupdateEntranceExit(screenCoords); break; } } void OnToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override { switch (widgetIndex) { case WIDX_CONSTRUCT: RideConstructionTooldownConstruct(screenCoords); break; case WIDX_ENTRANCE: case WIDX_EXIT: ToolDownEntranceExit(screenCoords); break; } } void OnPrepareDraw() override { auto currentRide = GetRide(_currentRideIndex); if (currentRide == nullptr) { return; } StringId stringId = STR_RIDE_CONSTRUCTION_SPECIAL; if (_currentTrackCurve & RideConstructionSpecialPieceSelected) { const auto& rtd = currentRide->GetRideTypeDescriptor(); const auto& ted = GetTrackElementDescriptor(_currentTrackCurve & ~RideConstructionSpecialPieceSelected); stringId = ted.Description; if (stringId == STR_RAPIDS && rtd.Category != RIDE_CATEGORY_WATER) { stringId = STR_LOG_BUMPS; } } auto ft = Formatter::Common(); ft.Add(stringId); if (_currentlyShowingBrakeOrBoosterSpeed) { uint16_t brakeSpeed2 = ((_currentBrakeSpeed2 * 9) >> 2) & 0xFFFF; if (TrackTypeIsBooster(_selectedTrackType) || TrackTypeIsBooster(_currentTrackCurve & ~RideConstructionSpecialPieceSelected)) { brakeSpeed2 = GetBoosterSpeed(currentRide->type, brakeSpeed2); } ft.Add(brakeSpeed2); } widgets[WIDX_SEAT_ROTATION_ANGLE_SPINNER].text = RideConstructionSeatAngleRotationStrings [_currentSeatRotationAngle]; // Simulate button auto& simulateWidget = widgets[WIDX_SIMULATE]; simulateWidget.type = WindowWidgetType::Empty; if (currentRide->SupportsStatus(RideStatus::Simulating)) { simulateWidget.type = WindowWidgetType::FlatBtn; if (currentRide->status == RideStatus::Simulating) { pressed_widgets |= (1uLL << WIDX_SIMULATE); } else { pressed_widgets &= ~(1uLL << WIDX_SIMULATE); } } // Set window title arguments ft = Formatter::Common(); ft.Increment(4); currentRide->FormatNameTo(ft); } void OnDraw(DrawPixelInfo& dpi) override { DrawPixelInfo clipdpi; Widget* widget; int32_t widgetWidth, widgetHeight; DrawWidgets(dpi); widget = &widgets[WIDX_CONSTRUCT]; if (widget->type == WindowWidgetType::Empty) return; RideId rideIndex; int32_t trackType, trackDirection, liftHillAndInvertedState; if (WindowRideConstructionUpdateState( &trackType, &trackDirection, &rideIndex, &liftHillAndInvertedState, nullptr, nullptr)) return; // Draw track piece auto screenCoords = ScreenCoordsXY{ windowPos.x + widget->left + 1, windowPos.y + widget->top + 1 }; widgetWidth = widget->width() - 1; widgetHeight = widget->height() - 1; if (ClipDrawPixelInfo(clipdpi, dpi, screenCoords, widgetWidth, widgetHeight)) { DrawTrackPiece( clipdpi, rideIndex, trackType, trackDirection, liftHillAndInvertedState, widgetWidth, widgetHeight); } // Draw cost screenCoords = { windowPos.x + widget->midX(), windowPos.y + widget->bottom - 23 }; if (_rideConstructionState != RideConstructionState::Place) DrawTextBasic(dpi, screenCoords, STR_BUILD_THIS, {}, { TextAlignment::CENTRE }); screenCoords.y += 11; if (_currentTrackPrice != kMoney64Undefined && !(GetGameState().Park.Flags & PARK_FLAGS_NO_MONEY)) { auto ft = Formatter(); ft.Add(_currentTrackPrice); DrawTextBasic(dpi, screenCoords, STR_COST_LABEL, ft, { TextAlignment::CENTRE }); } } void UpdateWidgets() { auto currentRide = GetRide(_currentRideIndex); if (currentRide == nullptr) { return; } const auto& rtd = GetRideTypeDescriptor(currentRide->type); auto trackDrawerDescriptor = getCurrentTrackDrawerDescriptor(rtd); hold_down_widgets = 0; if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_SHOP_OR_FACILITY) || !currentRide->HasStation()) { widgets[WIDX_ENTRANCE_EXIT_GROUPBOX].type = WindowWidgetType::Empty; widgets[WIDX_ENTRANCE].type = WindowWidgetType::Empty; widgets[WIDX_EXIT].type = WindowWidgetType::Empty; } else { widgets[WIDX_ENTRANCE_EXIT_GROUPBOX].type = WindowWidgetType::Groupbox; widgets[WIDX_ENTRANCE].type = WindowWidgetType::Button; widgets[WIDX_EXIT].type = WindowWidgetType::Button; } if (_specialElementDropdownState.HasActiveElements) { widgets[WIDX_SPECIAL_TRACK_DROPDOWN].type = WindowWidgetType::Button; } else { widgets[WIDX_SPECIAL_TRACK_DROPDOWN].type = WindowWidgetType::Empty; } if (IsTrackEnabled(TRACK_STRAIGHT)) { widgets[WIDX_STRAIGHT].type = WindowWidgetType::FlatBtn; } else { widgets[WIDX_STRAIGHT].type = WindowWidgetType::Empty; } if (IsTrackEnabled(TRACK_CURVE_LARGE)) { widgets[WIDX_LEFT_CURVE_LARGE].type = WindowWidgetType::FlatBtn; widgets[WIDX_RIGHT_CURVE_LARGE].type = WindowWidgetType::FlatBtn; } else { widgets[WIDX_LEFT_CURVE_LARGE].type = WindowWidgetType::Empty; widgets[WIDX_RIGHT_CURVE_LARGE].type = WindowWidgetType::Empty; } widgets[WIDX_LEFT_CURVE].type = WindowWidgetType::Empty; widgets[WIDX_RIGHT_CURVE].type = WindowWidgetType::Empty; widgets[WIDX_LEFT_CURVE_SMALL].type = WindowWidgetType::Empty; widgets[WIDX_RIGHT_CURVE_SMALL].type = WindowWidgetType::Empty; widgets[WIDX_LEFT_CURVE_VERY_SMALL].type = WindowWidgetType::Empty; widgets[WIDX_RIGHT_CURVE_VERY_SMALL].type = WindowWidgetType::Empty; if (IsTrackEnabled(TRACK_CURVE_VERTICAL)) { widgets[WIDX_LEFT_CURVE_SMALL].type = WindowWidgetType::FlatBtn; widgets[WIDX_RIGHT_CURVE_SMALL].type = WindowWidgetType::FlatBtn; } if (IsTrackEnabled(TRACK_CURVE)) { widgets[WIDX_LEFT_CURVE].type = WindowWidgetType::FlatBtn; widgets[WIDX_RIGHT_CURVE].type = WindowWidgetType::FlatBtn; } if (IsTrackEnabled(TRACK_CURVE_SMALL)) { widgets[WIDX_LEFT_CURVE_SMALL].type = WindowWidgetType::FlatBtn; widgets[WIDX_RIGHT_CURVE_SMALL].type = WindowWidgetType::FlatBtn; } if (IsTrackEnabled(TRACK_CURVE_VERY_SMALL)) { widgets[WIDX_LEFT_CURVE_VERY_SMALL].type = WindowWidgetType::FlatBtn; widgets[WIDX_RIGHT_CURVE_VERY_SMALL].type = WindowWidgetType::FlatBtn; } widgets[WIDX_SLOPE_DOWN_STEEP].type = WindowWidgetType::Empty; widgets[WIDX_SLOPE_DOWN].type = WindowWidgetType::Empty; widgets[WIDX_LEVEL].type = WindowWidgetType::Empty; widgets[WIDX_SLOPE_UP].type = WindowWidgetType::Empty; widgets[WIDX_SLOPE_UP_STEEP].type = WindowWidgetType::Empty; widgets[WIDX_SLOPE_DOWN_STEEP].image = ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_DOWN_STEEP); widgets[WIDX_SLOPE_DOWN_STEEP].tooltip = STR_RIDE_CONSTRUCTION_STEEP_SLOPE_DOWN_TIP; widgets[WIDX_SLOPE_UP_STEEP].image = ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_UP_STEEP); widgets[WIDX_SLOPE_UP_STEEP].tooltip = STR_RIDE_CONSTRUCTION_STEEP_SLOPE_UP_TIP; if (trackDrawerDescriptor.SupportsTrackPiece(TRACK_REVERSE_FREEFALL)) { widgets[WIDX_LEVEL].type = WindowWidgetType::FlatBtn; widgets[WIDX_SLOPE_UP].type = WindowWidgetType::FlatBtn; } if (IsTrackEnabled(TRACK_SLOPE) || IsTrackEnabled(TRACK_SLOPE_STEEP_DOWN) || IsTrackEnabled(TRACK_SLOPE_STEEP_UP)) { widgets[WIDX_LEVEL].type = WindowWidgetType::FlatBtn; } if (IsTrackEnabled(TRACK_SLOPE)) { widgets[WIDX_SLOPE_DOWN].type = WindowWidgetType::FlatBtn; widgets[WIDX_SLOPE_UP].type = WindowWidgetType::FlatBtn; } if ((IsTrackEnabled(TRACK_HELIX_DOWN_BANKED_HALF) || IsTrackEnabled(TRACK_HELIX_UP_BANKED_HALF)) && _currentTrackRollEnd != TrackRoll::None && _currentTrackPitchEnd == TrackPitch::None) { if (_currentTrackCurve >= EnumValue(TrackCurve::Left) && _currentTrackCurve <= EnumValue(TrackCurve::RightSmall)) { // Enable helix widgets[WIDX_SLOPE_DOWN_STEEP].type = WindowWidgetType::FlatBtn; if (currentRide->type != RIDE_TYPE_SPLASH_BOATS && currentRide->type != RIDE_TYPE_RIVER_RAFTS) widgets[WIDX_SLOPE_UP_STEEP].type = WindowWidgetType::FlatBtn; } } if (IsTrackEnabled(TRACK_SLOPE_STEEP_DOWN)) { widgets[WIDX_SLOPE_DOWN_STEEP].type = WindowWidgetType::FlatBtn; } if (IsTrackEnabled(TRACK_SLOPE_STEEP_UP)) { widgets[WIDX_SLOPE_UP_STEEP].type = WindowWidgetType::FlatBtn; } if (currentRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_UP_INCLINE_REQUIRES_LIFT) && (_currentTrackPitchEnd == TrackPitch::Up25 || _currentTrackPitchEnd == TrackPitch::Up60) && !GetGameState().Cheats.EnableAllDrawableTrackPieces) { _currentTrackLiftHill |= CONSTRUCTION_LIFT_HILL_SELECTED; } if ((IsTrackEnabled(TRACK_LIFT_HILL) && (_currentTrackCurve & RideConstructionSpecialPieceSelected) == 0) || (GetGameState().Cheats.EnableChainLiftOnAllTrack && currentRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK))) { widgets[WIDX_CHAIN_LIFT].type = WindowWidgetType::FlatBtn; } else { widgets[WIDX_CHAIN_LIFT].type = WindowWidgetType::Empty; } int32_t x = 45; for (int32_t i = WIDX_SLOPE_DOWN_STEEP; i <= WIDX_SLOPE_UP_STEEP; i++) { widgets[i].left = x; widgets[i].right = x + 23; x += 24; } widgets[WIDX_SLOPE_UP_STEEP].image = ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_UP_STEEP); widgets[WIDX_SLOPE_UP_STEEP].tooltip = STR_RIDE_CONSTRUCTION_STEEP_SLOPE_UP_TIP; widgets[WIDX_SLOPE_DOWN_STEEP].image = ImageId(SPR_RIDE_CONSTRUCTION_SLOPE_DOWN_STEEP); widgets[WIDX_SLOPE_DOWN_STEEP].tooltip = STR_RIDE_CONSTRUCTION_STEEP_SLOPE_DOWN_TIP; if (IsTrackEnabled(TRACK_SLOPE_VERTICAL)) { if (_previousTrackPitchEnd == TrackPitch::Up60 || _previousTrackPitchEnd == TrackPitch::Up90) { int32_t originalSlopeUpSteepLeft = widgets[WIDX_SLOPE_UP_STEEP].left; int32_t originalSlopeUpSteepRight = widgets[WIDX_SLOPE_UP_STEEP].right; for (int32_t i = WIDX_SLOPE_UP_STEEP; i > WIDX_SLOPE_DOWN_STEEP; i--) { widgets[i].left = widgets[i - 1].left; widgets[i].right = widgets[i - 1].right; } widgets[WIDX_SLOPE_DOWN_STEEP].left = originalSlopeUpSteepLeft; widgets[WIDX_SLOPE_DOWN_STEEP].right = originalSlopeUpSteepRight; widgets[WIDX_SLOPE_DOWN_STEEP].image = ImageId(SPR_RIDE_CONSTRUCTION_VERTICAL_RISE); widgets[WIDX_SLOPE_DOWN_STEEP].tooltip = STR_RIDE_CONSTRUCTION_VERTICAL_RISE_TIP; } else if (_previousTrackPitchEnd == TrackPitch::Down60 || _previousTrackPitchEnd == TrackPitch::Down90) { int32_t originalSlopeDownSteepLeft = widgets[WIDX_SLOPE_DOWN_STEEP].left; int32_t originalSlopeDownSteepRight = widgets[WIDX_SLOPE_DOWN_STEEP].right; for (int32_t i = WIDX_SLOPE_DOWN_STEEP; i < WIDX_SLOPE_UP_STEEP; i++) { widgets[i].left = widgets[i + 1].left; widgets[i].right = widgets[i + 1].right; } widgets[WIDX_SLOPE_UP_STEEP].left = originalSlopeDownSteepLeft; widgets[WIDX_SLOPE_UP_STEEP].right = originalSlopeDownSteepRight; widgets[WIDX_SLOPE_UP_STEEP].image = ImageId(SPR_RIDE_CONSTRUCTION_VERTICAL_DROP); widgets[WIDX_SLOPE_UP_STEEP].tooltip = STR_RIDE_CONSTRUCTION_VERTICAL_DROP_TIP; } } if ((IsTrackEnabled(TRACK_HELIX_DOWN_UNBANKED_QUARTER) || IsTrackEnabled(TRACK_HELIX_UP_UNBANKED_QUARTER)) && _currentTrackPitchEnd == TrackPitch::None && _currentTrackRollEnd == TrackRoll::None && (_currentTrackCurve == EnumValue(TrackCurve::Left) || _currentTrackCurve == EnumValue(TrackCurve::Right))) { widgets[WIDX_SLOPE_DOWN_STEEP].image = ImageId(SPR_RIDE_CONSTRUCTION_HELIX_DOWN); widgets[WIDX_SLOPE_DOWN_STEEP].tooltip = STR_RIDE_CONSTRUCTION_HELIX_DOWN_TIP; widgets[WIDX_SLOPE_UP_STEEP].image = ImageId(SPR_RIDE_CONSTRUCTION_HELIX_UP); widgets[WIDX_SLOPE_UP_STEEP].tooltip = STR_RIDE_CONSTRUCTION_HELIX_UP_TIP; int32_t tmp = widgets[WIDX_SLOPE_DOWN_STEEP].left; widgets[WIDX_SLOPE_DOWN_STEEP].left = widgets[WIDX_SLOPE_DOWN].left; widgets[WIDX_SLOPE_DOWN].left = tmp; tmp = widgets[WIDX_SLOPE_DOWN_STEEP].right; widgets[WIDX_SLOPE_DOWN_STEEP].right = widgets[WIDX_SLOPE_DOWN].right; widgets[WIDX_SLOPE_DOWN].right = tmp; tmp = widgets[WIDX_SLOPE_UP_STEEP].left; widgets[WIDX_SLOPE_UP_STEEP].left = widgets[WIDX_SLOPE_UP].left; widgets[WIDX_SLOPE_UP].left = tmp; tmp = widgets[WIDX_SLOPE_UP_STEEP].right; widgets[WIDX_SLOPE_UP_STEEP].right = widgets[WIDX_SLOPE_UP].right; widgets[WIDX_SLOPE_UP].right = tmp; } if ((IsTrackEnabled(TRACK_HELIX_DOWN_BANKED_QUARTER) || IsTrackEnabled(TRACK_HELIX_UP_BANKED_QUARTER) || IsTrackEnabled(TRACK_HELIX_DOWN_BANKED_HALF) || IsTrackEnabled(TRACK_HELIX_UP_BANKED_HALF)) && (_currentTrackCurve >= EnumValue(TrackCurve::Left) && _currentTrackCurve <= EnumValue(TrackCurve::RightSmall)) && _currentTrackPitchEnd == TrackPitch::None && _currentTrackRollEnd != TrackRoll::None) { widgets[WIDX_SLOPE_DOWN_STEEP].image = ImageId(SPR_RIDE_CONSTRUCTION_HELIX_DOWN); widgets[WIDX_SLOPE_DOWN_STEEP].tooltip = STR_RIDE_CONSTRUCTION_HELIX_DOWN_TIP; widgets[WIDX_SLOPE_UP_STEEP].image = ImageId(SPR_RIDE_CONSTRUCTION_HELIX_UP); widgets[WIDX_SLOPE_UP_STEEP].tooltip = STR_RIDE_CONSTRUCTION_HELIX_UP_TIP; int32_t tmp = widgets[WIDX_SLOPE_DOWN_STEEP].left; widgets[WIDX_SLOPE_DOWN_STEEP].left = widgets[WIDX_SLOPE_DOWN].left; widgets[WIDX_SLOPE_DOWN].left = tmp; tmp = widgets[WIDX_SLOPE_DOWN_STEEP].right; widgets[WIDX_SLOPE_DOWN_STEEP].right = widgets[WIDX_SLOPE_DOWN].right; widgets[WIDX_SLOPE_DOWN].right = tmp; tmp = widgets[WIDX_SLOPE_UP_STEEP].left; widgets[WIDX_SLOPE_UP_STEEP].left = widgets[WIDX_SLOPE_UP].left; widgets[WIDX_SLOPE_UP].left = tmp; tmp = widgets[WIDX_SLOPE_UP_STEEP].right; widgets[WIDX_SLOPE_UP_STEEP].right = widgets[WIDX_SLOPE_UP].right; widgets[WIDX_SLOPE_UP].right = tmp; } widgets[WIDX_BANKING_GROUPBOX].image = ImageId(STR_RIDE_CONSTRUCTION_ROLL_BANKING); widgets[WIDX_BANK_LEFT].image = ImageId(SPR_RIDE_CONSTRUCTION_LEFT_BANK); widgets[WIDX_BANK_LEFT].tooltip = STR_RIDE_CONSTRUCTION_ROLL_FOR_LEFT_CURVE_TIP; widgets[WIDX_BANK_LEFT].left = 69; widgets[WIDX_BANK_LEFT].right = 92; widgets[WIDX_BANK_LEFT].top = 132; widgets[WIDX_BANK_LEFT].bottom = 155; widgets[WIDX_BANK_STRAIGHT].image = ImageId(SPR_RIDE_CONSTRUCTION_NO_BANK); widgets[WIDX_BANK_STRAIGHT].tooltip = STR_RIDE_CONSTRUCTION_NO_ROLL_TIP; widgets[WIDX_BANK_STRAIGHT].left = 93; widgets[WIDX_BANK_STRAIGHT].right = 116; widgets[WIDX_BANK_STRAIGHT].top = 132; widgets[WIDX_BANK_STRAIGHT].bottom = 155; widgets[WIDX_BANK_RIGHT].image = ImageId(SPR_RIDE_CONSTRUCTION_RIGHT_BANK); widgets[WIDX_BANK_RIGHT].tooltip = STR_RIDE_CONSTRUCTION_ROLL_FOR_RIGHT_CURVE_TIP; widgets[WIDX_BANK_RIGHT].left = 117; widgets[WIDX_BANK_RIGHT].right = 140; widgets[WIDX_BANK_RIGHT].top = 132; widgets[WIDX_BANK_RIGHT].bottom = 155; widgets[WIDX_BANK_LEFT].type = WindowWidgetType::Empty; widgets[WIDX_BANK_STRAIGHT].type = WindowWidgetType::Empty; widgets[WIDX_BANK_RIGHT].type = WindowWidgetType::Empty; widgets[WIDX_U_TRACK].type = WindowWidgetType::Empty; widgets[WIDX_O_TRACK].type = WindowWidgetType::Empty; bool trackHasSpeedSetting = TrackTypeHasSpeedSetting(_selectedTrackType) || TrackTypeHasSpeedSetting(_currentTrackCurve & ~RideConstructionSpecialPieceSelected); bool boosterTrackSelected = TrackTypeIsBooster(_selectedTrackType) || TrackTypeIsBooster(_currentTrackCurve & ~RideConstructionSpecialPieceSelected); // Only necessary because TD6 writes speed and seat rotation to the same bits. Remove for new track design format. bool trackHasSpeedAndSeatRotation = _selectedTrackType == TrackElemType::BlockBrakes || _currentTrackCurve == (RideConstructionSpecialPieceSelected | TrackElemType::BlockBrakes) || _selectedTrackType > TrackElemType::HighestAlias || _currentTrackCurve > (RideConstructionSpecialPieceSelected | TrackElemType::HighestAlias); bool rideHasSeatRotation = rtd.HasFlag(RIDE_TYPE_FLAG_HAS_SEAT_ROTATION); if (!trackHasSpeedSetting) { if (IsTrackEnabled(TRACK_FLAT_ROLL_BANKING)) { widgets[WIDX_BANK_LEFT].type = WindowWidgetType::FlatBtn; widgets[WIDX_BANK_STRAIGHT].type = WindowWidgetType::FlatBtn; widgets[WIDX_BANK_RIGHT].type = WindowWidgetType::FlatBtn; } if (trackDrawerDescriptor.HasCoveredPieces()) { widgets[WIDX_BANKING_GROUPBOX].text = STR_RIDE_CONSTRUCTION_TRACK_STYLE; widgets[WIDX_U_TRACK].type = WindowWidgetType::FlatBtn; widgets[WIDX_O_TRACK].type = WindowWidgetType::FlatBtn; if (rtd.Category == RIDE_CATEGORY_WATER) { widgets[WIDX_U_TRACK].image = ImageId(SPR_RIDE_CONSTRUCTION_U_SHAPED_TRACK); widgets[WIDX_O_TRACK].image = ImageId(SPR_RIDE_CONSTRUCTION_O_SHAPED_TRACK); widgets[WIDX_U_TRACK].tooltip = STR_RIDE_CONSTRUCTION_U_SHAPED_OPEN_TRACK_TIP; widgets[WIDX_O_TRACK].tooltip = STR_RIDE_CONSTRUCTION_O_SHAPED_ENCLOSED_TRACK_TIP; } else { widgets[WIDX_U_TRACK].image = ImageId(SPR_RIDE_CONSTRUCTION_RC_TRACK); widgets[WIDX_O_TRACK].image = ImageId(SPR_RIDE_CONSTRUCTION_WATER_CHANNEL); widgets[WIDX_U_TRACK].tooltip = STR_RIDE_CONSTRUCTION_STANDARD_RC_TRACK_TIP; widgets[WIDX_O_TRACK].tooltip = STR_RIDE_CONSTRUCTION_WATER_CHANNEL_TIP; } // TODO: Read these from the TrackDrawerEntry if (currentRide->type == RIDE_TYPE_WATER_COASTER) { const bool hasCoveredVersion = (_currentTrackCurve < EnumValue(TrackCurve::LeftSmall) || _currentTrackCurve == (RideConstructionSpecialPieceSelected | TrackElemType::SBendLeft) || _currentTrackCurve == (RideConstructionSpecialPieceSelected | TrackElemType::SBendRight)) && _currentTrackPitchEnd == TrackPitch::None && _currentTrackRollEnd == TrackRoll::None; if (!hasCoveredVersion) { widgets[WIDX_U_TRACK].type = WindowWidgetType::Empty; widgets[WIDX_O_TRACK].type = WindowWidgetType::Empty; } } } } else { if (!boosterTrackSelected) { widgets[WIDX_SPEED_GROUPBOX].text = STR_RIDE_CONSTRUCTION_BRAKE_SPEED; widgets[WIDX_SPEED_SETTING_SPINNER].tooltip = STR_RIDE_CONSTRUCTION_BRAKE_SPEED_LIMIT_TIP; widgets[WIDX_SPEED_SETTING_SPINNER_UP].tooltip = STR_RIDE_CONSTRUCTION_BRAKE_SPEED_LIMIT_TIP; widgets[WIDX_SPEED_SETTING_SPINNER_DOWN].tooltip = STR_RIDE_CONSTRUCTION_BRAKE_SPEED_LIMIT_TIP; } else { widgets[WIDX_SPEED_GROUPBOX].text = STR_RIDE_CONSTRUCTION_BOOSTER_SPEED; widgets[WIDX_SPEED_SETTING_SPINNER].tooltip = STR_RIDE_CONSTRUCTION_BOOSTER_SPEED_LIMIT_TIP; widgets[WIDX_SPEED_SETTING_SPINNER_UP].tooltip = STR_RIDE_CONSTRUCTION_BOOSTER_SPEED_LIMIT_TIP; widgets[WIDX_SPEED_SETTING_SPINNER_DOWN].tooltip = STR_RIDE_CONSTRUCTION_BOOSTER_SPEED_LIMIT_TIP; } _currentlyShowingBrakeOrBoosterSpeed = true; widgets[WIDX_SPEED_SETTING_SPINNER].text = STR_RIDE_CONSTRUCTION_BRAKE_SPEED_VELOCITY; widgets[WIDX_SPEED_SETTING_SPINNER].type = WindowWidgetType::Spinner; widgets[WIDX_SPEED_SETTING_SPINNER_UP].type = WindowWidgetType::Button; widgets[WIDX_SPEED_SETTING_SPINNER_UP].text = STR_NUMERIC_UP; widgets[WIDX_SPEED_SETTING_SPINNER_DOWN].type = WindowWidgetType::Button; widgets[WIDX_SPEED_SETTING_SPINNER_DOWN].text = STR_NUMERIC_DOWN; ResizeSpinner(WIDX_SPEED_SETTING_SPINNER, { 12, 138 }, { 85, SPINNER_HEIGHT }); hold_down_widgets |= (1uLL << WIDX_SPEED_SETTING_SPINNER_UP) | (1uLL << WIDX_SPEED_SETTING_SPINNER_DOWN); } static constexpr int16_t bankingGroupboxRightNoSeatRotation = GW; static constexpr int16_t bankingGroupboxRightWithSeatRotation = 114; widgets[WIDX_BANKING_GROUPBOX].right = bankingGroupboxRightNoSeatRotation; widgets[WIDX_SEAT_ROTATION_GROUPBOX].type = WindowWidgetType::Empty; widgets[WIDX_SEAT_ROTATION_ANGLE_SPINNER].type = WindowWidgetType::Empty; widgets[WIDX_SEAT_ROTATION_ANGLE_SPINNER_UP].type = WindowWidgetType::Empty; widgets[WIDX_SEAT_ROTATION_ANGLE_SPINNER_DOWN].type = WindowWidgetType::Empty; // Simplify this condition to "rideHasSeatRotation" for new track design format if ((rideHasSeatRotation && !trackHasSpeedSetting) || (rideHasSeatRotation && trackHasSpeedSetting && trackHasSpeedAndSeatRotation)) { widgets[WIDX_SEAT_ROTATION_GROUPBOX].type = WindowWidgetType::Groupbox; widgets[WIDX_SEAT_ROTATION_ANGLE_SPINNER].type = WindowWidgetType::Spinner; widgets[WIDX_SEAT_ROTATION_ANGLE_SPINNER_UP].type = WindowWidgetType::Button; widgets[WIDX_SEAT_ROTATION_ANGLE_SPINNER_DOWN].type = WindowWidgetType::Button; widgets[WIDX_BANKING_GROUPBOX].right = bankingGroupboxRightWithSeatRotation; // squishes the track speed spinner slightly to make room for the seat rotation widgets if (trackHasSpeedSetting) { widgets[WIDX_SPEED_SETTING_SPINNER].left -= 4; widgets[WIDX_SPEED_SETTING_SPINNER].right -= 8; widgets[WIDX_SPEED_SETTING_SPINNER_UP].right -= 8; widgets[WIDX_SPEED_SETTING_SPINNER_DOWN].right -= 8; widgets[WIDX_SPEED_SETTING_SPINNER_UP].left -= 8; widgets[WIDX_SPEED_SETTING_SPINNER_DOWN].left -= 8; } // moves banking buttons to the left to make room for the seat rotation widgets else if (IsTrackEnabled(TRACK_FLAT_ROLL_BANKING)) { for (int32_t i = WIDX_BANK_LEFT; i <= WIDX_BANK_RIGHT; i++) { widgets[i].left -= 36; widgets[i].right -= 36; } } } uint64_t pressedWidgets = pressed_widgets & ((1uLL << WIDX_BACKGROUND) | (1uLL << WIDX_TITLE) | (1uLL << WIDX_CLOSE) | (1uLL << WIDX_DIRECTION_GROUPBOX) | (1uLL << WIDX_SLOPE_GROUPBOX) | (1uLL << WIDX_BANKING_GROUPBOX) | (1uLL << WIDX_CONSTRUCT) | (1uLL << WIDX_DEMOLISH) | (1uLL << WIDX_PREVIOUS_SECTION) | (1uLL << WIDX_NEXT_SECTION) | (1uLL << WIDX_ENTRANCE_EXIT_GROUPBOX) | (1uLL << WIDX_ENTRANCE) | (1uLL << WIDX_EXIT)); widgets[WIDX_CONSTRUCT].type = WindowWidgetType::Empty; widgets[WIDX_DEMOLISH].type = WindowWidgetType::FlatBtn; widgets[WIDX_ROTATE].type = WindowWidgetType::Empty; if (rtd.HasFlag(RIDE_TYPE_FLAG_CANNOT_HAVE_GAPS)) { widgets[WIDX_PREVIOUS_SECTION].type = WindowWidgetType::Empty; widgets[WIDX_NEXT_SECTION].type = WindowWidgetType::Empty; } else { widgets[WIDX_PREVIOUS_SECTION].type = WindowWidgetType::FlatBtn; widgets[WIDX_NEXT_SECTION].type = WindowWidgetType::FlatBtn; } switch (_rideConstructionState) { case RideConstructionState::Front: widgets[WIDX_CONSTRUCT].type = WindowWidgetType::ImgBtn; widgets[WIDX_NEXT_SECTION].type = WindowWidgetType::Empty; break; case RideConstructionState::Back: widgets[WIDX_CONSTRUCT].type = WindowWidgetType::ImgBtn; widgets[WIDX_PREVIOUS_SECTION].type = WindowWidgetType::Empty; break; case RideConstructionState::Place: widgets[WIDX_CONSTRUCT].type = WindowWidgetType::ImgBtn; widgets[WIDX_DEMOLISH].type = WindowWidgetType::Empty; widgets[WIDX_NEXT_SECTION].type = WindowWidgetType::Empty; widgets[WIDX_PREVIOUS_SECTION].type = WindowWidgetType::Empty; widgets[WIDX_ROTATE].type = WindowWidgetType::FlatBtn; break; case RideConstructionState::EntranceExit: widgets[WIDX_DEMOLISH].type = WindowWidgetType::Empty; widgets[WIDX_NEXT_SECTION].type = WindowWidgetType::Empty; widgets[WIDX_PREVIOUS_SECTION].type = WindowWidgetType::Empty; break; default: pressed_widgets = pressedWidgets; Invalidate(); return; } WidgetIndex widgetIndex; switch (_currentTrackCurve) { case EnumValue(TrackCurve::None): widgetIndex = WIDX_STRAIGHT; break; case EnumValue(TrackCurve::Left): widgetIndex = WIDX_LEFT_CURVE; break; case EnumValue(TrackCurve::Right): widgetIndex = WIDX_RIGHT_CURVE; break; case EnumValue(TrackCurve::LeftSmall): widgetIndex = WIDX_LEFT_CURVE_SMALL; break; case EnumValue(TrackCurve::RightSmall): widgetIndex = WIDX_RIGHT_CURVE_SMALL; break; case EnumValue(TrackCurve::LeftVerySmall): widgetIndex = WIDX_LEFT_CURVE_VERY_SMALL; break; case EnumValue(TrackCurve::RightVerySmall): widgetIndex = WIDX_RIGHT_CURVE_VERY_SMALL; break; case EnumValue(TrackCurve::LeftLarge): widgetIndex = WIDX_LEFT_CURVE_LARGE; break; case EnumValue(TrackCurve::RightLarge): widgetIndex = WIDX_RIGHT_CURVE_LARGE; break; default: widgetIndex = WIDX_SPECIAL_TRACK_DROPDOWN; break; } pressedWidgets |= (1uLL << widgetIndex); switch (_currentTrackPitchEnd) { case TrackPitch::Down60: case TrackPitch::Up90: widgetIndex = WIDX_SLOPE_DOWN_STEEP; break; case TrackPitch::Down25: widgetIndex = WIDX_SLOPE_DOWN; break; case TrackPitch::Up25: widgetIndex = WIDX_SLOPE_UP; break; case TrackPitch::Up60: case TrackPitch::Down90: widgetIndex = WIDX_SLOPE_UP_STEEP; break; default: widgetIndex = WIDX_LEVEL; break; } pressedWidgets |= (1uLL << widgetIndex); if (!_currentlyShowingBrakeOrBoosterSpeed) { if (trackDrawerDescriptor.HasCoveredPieces()) { if (_currentTrackAlternative & RIDE_TYPE_ALTERNATIVE_TRACK_PIECES) { pressed_widgets |= (1uLL << WIDX_O_TRACK); } else { pressed_widgets |= (1uLL << WIDX_U_TRACK); } } switch (_currentTrackRollEnd) { case TrackRoll::Left: widgetIndex = WIDX_BANK_LEFT; break; case TrackRoll::None: widgetIndex = WIDX_BANK_STRAIGHT; break; default: widgetIndex = WIDX_BANK_RIGHT; break; } pressedWidgets |= (1uLL << widgetIndex); } if (_currentTrackLiftHill & CONSTRUCTION_LIFT_HILL_SELECTED) pressedWidgets |= (1uLL << WIDX_CHAIN_LIFT); pressed_widgets = pressedWidgets; Invalidate(); } void UpdatePossibleRideConfigurations() { auto currentRide = GetRide(_currentRideIndex); if (currentRide == nullptr) { return; } _specialElementDropdownState = BuildSpecialElementsList( *currentRide, _currentTrackPieceDirection, _previousTrackPitchEnd, _previousTrackRollEnd, _rideConstructionState); _currentlyShowingBrakeOrBoosterSpeed = false; } void UpdateMapSelection() { int32_t trackType, trackDirection; CoordsXYZ trackPos{}; MapInvalidateMapSelectionTiles(); gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_CONSTRUCT; gMapSelectFlags |= MAP_SELECT_FLAG_GREEN; switch (_rideConstructionState) { case RideConstructionState::State0: trackDirection = _currentTrackPieceDirection; trackType = 0; trackPos = _currentTrackBegin; break; case RideConstructionState::Selected: trackDirection = _currentTrackPieceDirection; trackType = _currentTrackPieceType; trackPos = _currentTrackBegin; break; case RideConstructionState::EntranceExit: gMapSelectionTiles.clear(); return; default: if (WindowRideConstructionUpdateState(&trackType, &trackDirection, nullptr, nullptr, &trackPos, nullptr)) { trackDirection = _currentTrackPieceDirection; trackType = 0; trackPos = _currentTrackBegin; } break; } if (GetRide(_currentRideIndex)) { SelectMapTiles(trackType, trackDirection, trackPos); MapInvalidateMapSelectionTiles(); } } void SelectMapTiles(int32_t trackType, int32_t trackDirection, const CoordsXY& tileCoords) { // If the scenery tool is active, we do not display our tiles as it // will conflict with larger scenery objects selecting tiles if (SceneryToolIsActive()) { return; } const PreviewTrack* trackBlock; const auto& ted = GetTrackElementDescriptor(trackType); trackBlock = ted.Block; trackDirection &= 3; gMapSelectionTiles.clear(); while (trackBlock->index != 255) { CoordsXY offsets = { trackBlock->x, trackBlock->y }; CoordsXY currentTileCoords = tileCoords + offsets.Rotate(trackDirection); gMapSelectionTiles.push_back(currentTileCoords); trackBlock++; } } private: void Construct() { RideId rideIndex; int32_t trackType, trackDirection, liftHillAndAlternativeState, properties; CoordsXYZ trackPos{}; _currentTrackPrice = kMoney64Undefined; _trackPlaceCost = kMoney64Undefined; _trackPlaceErrorMessage = STR_NONE; RideConstructionInvalidateCurrentTrack(); if (WindowRideConstructionUpdateState( &trackType, &trackDirection, &rideIndex, &liftHillAndAlternativeState, &trackPos, &properties)) { WindowRideConstructionUpdateActiveElements(); return; } auto currentRide = GetRide(_currentRideIndex); if (currentRide == nullptr) { return; } auto trackPlaceAction = TrackPlaceAction( rideIndex, trackType, currentRide->type, { trackPos, static_cast(trackDirection) }, properties & 0xFF, (properties >> 8) & 0x0F, (properties >> 12) & 0x0F, liftHillAndAlternativeState, false); if (_rideConstructionState == RideConstructionState::Back) { trackPlaceAction.SetCallback(RideConstructPlacedBackwardGameActionCallback); } else if (_rideConstructionState == RideConstructionState::Front) { trackPlaceAction.SetCallback(RideConstructPlacedForwardGameActionCallback); } auto res = GameActions::Execute(&trackPlaceAction); // Used by some functions if (res.Error != GameActions::Status::Ok) { _trackPlaceCost = kMoney64Undefined; _trackPlaceErrorMessage = std::get(res.ErrorMessage); } else { _trackPlaceCost = res.Cost; _trackPlaceErrorMessage = STR_NONE; } if (res.Error != GameActions::Status::Ok) { return; } OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::PlaceItem, trackPos); if (NetworkGetMode() != NETWORK_MODE_NONE) { _currentTrackSelectionFlags |= TRACK_SELECTION_FLAG_TRACK_PLACE_ACTION_QUEUED; } const auto resultData = res.GetData(); if (resultData.GroundFlags & ELEMENT_IS_UNDERGROUND) { ViewportSetVisibility(ViewportVisibility::UndergroundViewOn); } const bool helixSelected = (_currentTrackCurve & RideConstructionSpecialPieceSelected) && TrackTypeIsHelix(_currentTrackCurve & ~RideConstructionSpecialPieceSelected); if (helixSelected || (_currentTrackPitchEnd != TrackPitch::None)) { ViewportSetVisibility(ViewportVisibility::TrackHeights); } } void MouseUpDemolish() { int32_t direction; TileElement* tileElement; CoordsXYE inputElement, outputElement; TrackBeginEnd trackBeginEnd; _currentTrackPrice = kMoney64Undefined; RideConstructionInvalidateCurrentTrack(); // Select the track element that is to be deleted _rideConstructionState2 = RideConstructionState::Selected; if (_rideConstructionState == RideConstructionState::Front) { if (!RideSelectBackwardsFromFront()) { WindowRideConstructionUpdateActiveElements(); return; } _rideConstructionState2 = RideConstructionState::Front; } else if (_rideConstructionState == RideConstructionState::Back) { if (!RideSelectForwardsFromBack()) { WindowRideConstructionUpdateActiveElements(); return; } _rideConstructionState2 = RideConstructionState::Back; } // Invalidate the selected track element or make sure it's at origin??? direction = _currentTrackPieceDirection; // The direction is reset by ride_initialise_construction_window(), but we need it to remove flat rides properly. Direction currentDirection = _currentTrackPieceDirection; track_type_t type = _currentTrackPieceType; auto newCoords = GetTrackElementOriginAndApplyChanges( { _currentTrackBegin, static_cast(direction & 3) }, type, 0, &tileElement, 0); if (!newCoords.has_value()) { WindowRideConstructionUpdateActiveElements(); return; } // Get the previous track element to go to after the selected track element is deleted inputElement.x = newCoords->x; inputElement.y = newCoords->y; inputElement.element = tileElement; if (TrackBlockGetPrevious({ *newCoords, tileElement }, &trackBeginEnd)) { *newCoords = { trackBeginEnd.begin_x, trackBeginEnd.begin_y, trackBeginEnd.begin_z }; direction = trackBeginEnd.begin_direction; type = trackBeginEnd.begin_element->AsTrack()->GetTrackType(); _gotoStartPlacementMode = false; } else if (TrackBlockGetNext(&inputElement, &outputElement, &newCoords->z, &direction)) { newCoords->x = outputElement.x; newCoords->y = outputElement.y; direction = outputElement.element->GetDirection(); type = outputElement.element->AsTrack()->GetTrackType(); _gotoStartPlacementMode = false; } else { direction = _currentTrackPieceDirection; type = _currentTrackPieceType; newCoords = GetTrackElementOriginAndApplyChanges( { _currentTrackBegin, static_cast(direction & 3) }, type, 0, &tileElement, 0); if (!newCoords.has_value()) { WindowRideConstructionUpdateActiveElements(); return; } const auto& ted = GetTrackElementDescriptor(tileElement->AsTrack()->GetTrackType()); const PreviewTrack* trackBlock = ted.Block; newCoords->z = (tileElement->GetBaseZ()) - trackBlock->z; _gotoStartPlacementMode = true; // When flat rides are deleted, the window should be reset so the currentRide can be placed again. auto currentRide = GetRide(_currentRideIndex); const auto& rtd = currentRide->GetRideTypeDescriptor(); if (rtd.HasFlag(RIDE_TYPE_FLAG_FLAT_RIDE) && !rtd.HasFlag(RIDE_TYPE_FLAG_IS_SHOP_OR_FACILITY)) { RideInitialiseConstructionWindow(*currentRide); } } auto trackRemoveAction = TrackRemoveAction( _currentTrackPieceType, 0, { _currentTrackBegin.x, _currentTrackBegin.y, _currentTrackBegin.z, currentDirection }); trackRemoveAction.SetCallback([=](const GameAction* ga, const GameActions::Result* result) { if (result->Error != GameActions::Status::Ok) { WindowRideConstructionUpdateActiveElements(); } else { auto currentRide = GetRide(_currentRideIndex); if (currentRide != nullptr) { WindowRideConstructionMouseUpDemolishNextPiece({ *newCoords, static_cast(direction) }, type); } } }); GameActions::Execute(&trackRemoveAction); } void Rotate() { _autoRotatingShop = false; _currentTrackPieceDirection = (_currentTrackPieceDirection + 1) & 3; RideConstructionInvalidateCurrentTrack(); _currentTrackPrice = kMoney64Undefined; WindowRideConstructionUpdateActiveElements(); } void EntranceClick() { if (ToolSet(*this, WIDX_ENTRANCE, Tool::Crosshair)) { auto currentRide = GetRide(_currentRideIndex); if (currentRide != nullptr && !RideTryGetOriginElement(*currentRide, nullptr)) { RideInitialiseConstructionWindow(*currentRide); } } else { gRideEntranceExitPlaceType = ENTRANCE_TYPE_RIDE_ENTRANCE; gRideEntranceExitPlaceRideIndex = _currentRideIndex; gRideEntranceExitPlaceStationIndex = StationIndex::FromUnderlying(0); InputSetFlag(INPUT_FLAG_6, true); RideConstructionInvalidateCurrentTrack(); if (_rideConstructionState != RideConstructionState::EntranceExit) { gRideEntranceExitPlacePreviousRideConstructionState = _rideConstructionState; _rideConstructionState = RideConstructionState::EntranceExit; } WindowRideConstructionUpdateActiveElements(); } } void ExitClick() { if (ToolSet(*this, WIDX_EXIT, Tool::Crosshair)) { auto currentRide = GetRide(_currentRideIndex); if (!RideTryGetOriginElement(*currentRide, nullptr)) { RideInitialiseConstructionWindow(*currentRide); } } else { gRideEntranceExitPlaceType = ENTRANCE_TYPE_RIDE_EXIT; gRideEntranceExitPlaceRideIndex = _currentRideIndex; gRideEntranceExitPlaceStationIndex = StationIndex::FromUnderlying(0); InputSetFlag(INPUT_FLAG_6, true); RideConstructionInvalidateCurrentTrack(); if (_rideConstructionState != RideConstructionState::EntranceExit) { gRideEntranceExitPlacePreviousRideConstructionState = _rideConstructionState; _rideConstructionState = RideConstructionState::EntranceExit; } WindowRideConstructionUpdateActiveElements(); } } void UpdateLiftHillSelected(TrackPitch slope) { _currentTrackPitchEnd = slope; _currentTrackPrice = kMoney64Undefined; if (_rideConstructionState == RideConstructionState::Front && !GetGameState().Cheats.EnableChainLiftOnAllTrack) { switch (slope) { case TrackPitch::None: case TrackPitch::Up25: case TrackPitch::Up60: break; default: _currentTrackLiftHill &= ~CONSTRUCTION_LIFT_HILL_SELECTED; break; } } WindowRideConstructionUpdateActiveElements(); } void SetBrakeSpeed(int32_t brakesSpeed) { TileElement* tileElement; if (GetTrackElementOriginAndApplyChanges( { _currentTrackBegin, static_cast(_currentTrackPieceDirection & 3) }, _currentTrackPieceType, 0, &tileElement, 0) != std::nullopt) { auto trackSetBrakeSpeed = TrackSetBrakeSpeedAction( _currentTrackBegin, tileElement->AsTrack()->GetTrackType(), brakesSpeed); trackSetBrakeSpeed.SetCallback([](const GameAction* ga, const GameActions::Result* result) { WindowRideConstructionUpdateActiveElements(); }); GameActions::Execute(&trackSetBrakeSpeed); return; } WindowRideConstructionUpdateActiveElements(); } void ShowSpecialTrackDropdown(Widget* widget) { int32_t defaultIndex = -1; for (size_t i = 0; i < _specialElementDropdownState.Elements.size(); i++) { track_type_t trackPiece = _specialElementDropdownState.Elements[i].TrackType; const auto& ted = GetTrackElementDescriptor(trackPiece); StringId trackPieceStringId = ted.Description; if (trackPieceStringId == STR_RAPIDS) { auto currentRide = GetRide(_currentRideIndex); if (currentRide != nullptr) { const auto& rtd = currentRide->GetRideTypeDescriptor(); if (rtd.Category != RIDE_CATEGORY_WATER) trackPieceStringId = STR_LOG_BUMPS; } } gDropdownItems[i].Format = trackPieceStringId; if ((trackPiece | RideConstructionSpecialPieceSelected) == _currentTrackCurve) { defaultIndex = static_cast(i); } } WindowDropdownShowTextCustomWidth( { windowPos.x + widget->left, windowPos.y + widget->top }, widget->height() + 1, colours[1], 0, 0, _specialElementDropdownState.Elements.size(), widget->width()); for (size_t i = 0; i < _specialElementDropdownState.Elements.size(); i++) { Dropdown::SetDisabled(static_cast(i), _specialElementDropdownState.Elements[i].Disabled); } gDropdownDefaultIndex = defaultIndex; } void RideSelectedTrackSetSeatRotation(int32_t seatRotation) { GetTrackElementOriginAndApplyChanges( { _currentTrackBegin, static_cast(_currentTrackPieceDirection & 3) }, _currentTrackPieceType, seatRotation, nullptr, TRACK_ELEMENT_SET_SEAT_ROTATION); WindowRideConstructionUpdateActiveElements(); } void ToolDownEntranceExit(const ScreenCoordsXY& screenCoords) { RideConstructionInvalidateCurrentTrack(); MapInvalidateSelectionRect(); gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; CoordsXYZD entranceOrExitCoords = RideGetEntranceOrExitPositionFromScreenPosition(screenCoords); if (gRideEntranceExitPlaceDirection == INVALID_DIRECTION) return; auto rideEntranceExitPlaceAction = RideEntranceExitPlaceAction( entranceOrExitCoords, DirectionReverse(gRideEntranceExitPlaceDirection), gRideEntranceExitPlaceRideIndex, gRideEntranceExitPlaceStationIndex, gRideEntranceExitPlaceType == ENTRANCE_TYPE_RIDE_EXIT); rideEntranceExitPlaceAction.SetCallback([=](const GameAction* ga, const GameActions::Result* result) { if (result->Error != GameActions::Status::Ok) return; OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::PlaceItem, result->Position); auto currentRide = GetRide(gRideEntranceExitPlaceRideIndex); if (currentRide != nullptr && RideAreAllPossibleEntrancesAndExitsBuilt(*currentRide).Successful) { ToolCancel(); if (!currentRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_HAS_TRACK)) { WindowCloseByClass(WindowClass::RideConstruction); } } else { gRideEntranceExitPlaceType = gRideEntranceExitPlaceType ^ 1; WindowInvalidateByClass(WindowClass::RideConstruction); gCurrentToolWidget.widget_index = (gRideEntranceExitPlaceType == ENTRANCE_TYPE_RIDE_ENTRANCE) ? WC_RIDE_CONSTRUCTION__WIDX_ENTRANCE : WC_RIDE_CONSTRUCTION__WIDX_EXIT; } }); auto res = GameActions::Execute(&rideEntranceExitPlaceAction); } void DrawTrackPiece( DrawPixelInfo& dpi, RideId rideIndex, int32_t trackType, int32_t trackDirection, int32_t liftHillAndInvertedState, int32_t widgetWidth, int32_t widgetHeight) { auto currentRide = GetRide(rideIndex); if (currentRide == nullptr) { return; } const auto& ted = GetTrackElementDescriptor(trackType); const auto* trackBlock = ted.Block; while ((trackBlock + 1)->index != 0xFF) trackBlock++; CoordsXYZ mapCoords{ trackBlock->x, trackBlock->y, trackBlock->z }; if (trackBlock->flags & RCT_PREVIEW_TRACK_FLAG_1) { mapCoords.x = 0; mapCoords.y = 0; } auto rotatedMapCoords = mapCoords.Rotate(trackDirection); // this is actually case 0, but the other cases all jump to it mapCoords.x = 4112 + (rotatedMapCoords.x / 2); mapCoords.y = 4112 + (rotatedMapCoords.y / 2); mapCoords.z = 1024 + mapCoords.z; auto previewZOffset = ted.Definition.PreviewZOffset; mapCoords.z -= previewZOffset; const ScreenCoordsXY rotatedScreenCoords = Translate3DTo2DWithZ(GetCurrentRotation(), mapCoords); dpi.x += rotatedScreenCoords.x - widgetWidth / 2; dpi.y += rotatedScreenCoords.y - widgetHeight / 2 - 16; DrawTrackPieceHelper(dpi, rideIndex, trackType, trackDirection, liftHillAndInvertedState, { 4096, 4096 }, 1024); } void DrawTrackPieceHelper( DrawPixelInfo& dpi, RideId rideIndex, int32_t trackType, int32_t trackDirection, int32_t liftHillAndInvertedState, const CoordsXY& originCoords, int32_t originZ) { TileElement tempSideTrackTileElement{ 0x80, 0x8F, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; TileElement tempTrackTileElement{}; TileElement* backupTileElementArrays[5]{}; PaintSession* session = PaintSessionAlloc(dpi, 0, GetCurrentRotation()); trackDirection &= 3; auto currentRide = GetRide(rideIndex); if (currentRide == nullptr) { return; } auto& gameState = OpenRCT2::GetGameState(); auto preserveMapSize = gameState.MapSize; gameState.MapSize = { kMaximumMapSizeTechnical, kMaximumMapSizeTechnical }; // Setup non changing parts of the temporary track tile element tempTrackTileElement.SetType(TileElementType::Track); tempTrackTileElement.SetDirection(trackDirection); tempTrackTileElement.AsTrack()->SetHasChain((liftHillAndInvertedState & CONSTRUCTION_LIFT_HILL_SELECTED) != 0); tempTrackTileElement.SetLastForTile(true); tempTrackTileElement.AsTrack()->SetTrackType(trackType); tempTrackTileElement.AsTrack()->SetRideType(currentRide->type); tempTrackTileElement.AsTrack()->SetHasCableLift(false); tempTrackTileElement.AsTrack()->SetInverted((liftHillAndInvertedState & CONSTRUCTION_INVERTED_TRACK_SELECTED) != 0); tempTrackTileElement.AsTrack()->SetColourScheme(RIDE_COLOUR_SCHEME_MAIN); // Skipping seat rotation, should not be necessary for a temporary piece. tempTrackTileElement.AsTrack()->SetRideIndex(rideIndex); const auto& ted = GetTrackElementDescriptor(trackType); const auto* trackBlock = ted.Block; const auto* rideEntry = currentRide->GetRideEntry(); auto clearanceHeight = (rideEntry != nullptr) ? rideEntry->Clearance : currentRide->GetRideTypeDescriptor().Heights.ClearanceHeight; while (trackBlock->index != 255) { auto quarterTile = trackBlock->var_08.Rotate(trackDirection); CoordsXY offsets = { trackBlock->x, trackBlock->y }; CoordsXY coords = originCoords + offsets.Rotate(trackDirection); int32_t baseZ = originZ + trackBlock->z; int32_t clearanceZ = trackBlock->ClearanceZ + clearanceHeight + baseZ + (4 * COORDS_Z_STEP); auto centreTileCoords = TileCoordsXY{ coords }; auto eastTileCoords = centreTileCoords + TileDirectionDelta[TILE_ELEMENT_DIRECTION_EAST]; auto westTileCoords = centreTileCoords + TileDirectionDelta[TILE_ELEMENT_DIRECTION_WEST]; auto northTileCoords = centreTileCoords + TileDirectionDelta[TILE_ELEMENT_DIRECTION_NORTH]; auto southTileCoords = centreTileCoords + TileDirectionDelta[TILE_ELEMENT_DIRECTION_SOUTH]; // Replace map elements with temporary ones containing track backupTileElementArrays[0] = MapGetFirstElementAt(centreTileCoords); backupTileElementArrays[1] = MapGetFirstElementAt(eastTileCoords); backupTileElementArrays[2] = MapGetFirstElementAt(westTileCoords); backupTileElementArrays[3] = MapGetFirstElementAt(northTileCoords); backupTileElementArrays[4] = MapGetFirstElementAt(southTileCoords); MapSetTileElement(centreTileCoords, &tempTrackTileElement); MapSetTileElement(eastTileCoords, &tempSideTrackTileElement); MapSetTileElement(westTileCoords, &tempSideTrackTileElement); MapSetTileElement(northTileCoords, &tempSideTrackTileElement); MapSetTileElement(southTileCoords, &tempSideTrackTileElement); // Set the temporary track element tempTrackTileElement.SetOccupiedQuadrants(quarterTile.GetBaseQuarterOccupied()); tempTrackTileElement.SetBaseZ(baseZ); tempTrackTileElement.SetClearanceZ(clearanceZ); tempTrackTileElement.AsTrack()->SetSequenceIndex(trackBlock->index); // Draw this map tile TileElementPaintSetup(*session, coords, true); // Restore map elements MapSetTileElement(centreTileCoords, backupTileElementArrays[0]); MapSetTileElement(eastTileCoords, backupTileElementArrays[1]); MapSetTileElement(westTileCoords, backupTileElementArrays[2]); MapSetTileElement(northTileCoords, backupTileElementArrays[3]); MapSetTileElement(southTileCoords, backupTileElementArrays[4]); trackBlock++; } gameState.MapSize = preserveMapSize; PaintSessionArrange(*session); PaintDrawStructs(*session); PaintSessionFree(session); } }; static void WindowRideConstructionUpdateDisabledPieces(ObjectEntryIndex rideType) { RideTrackGroup disabledPieces{}; const auto& rtd = GetRideTypeDescriptor(rideType); if (rtd.HasFlag(RIDE_TYPE_FLAG_HAS_TRACK)) { // Set all pieces as “disabled”. When looping over the ride entries, // pieces will be re-enabled as soon as a single entry supports it. disabledPieces.flip(); auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); auto& rideEntries = objManager.GetAllRideEntries(rideType); // If there are no vehicles for this ride type, enable all the track pieces. if (rideEntries.size() == 0) { disabledPieces.reset(); } for (auto rideEntryIndex : rideEntries) { const auto* currentRideEntry = GetRideEntryByIndex(rideEntryIndex); if (currentRideEntry == nullptr) continue; // Non-default vehicle visuals do not use this system, so we have to assume it supports all the track pieces. if (currentRideEntry->Cars[0].PaintStyle != VEHICLE_VISUAL_DEFAULT || rideType == RIDE_TYPE_CHAIRLIFT || (currentRideEntry->Cars[0].flags & CAR_ENTRY_FLAG_SLIDE_SWING)) { disabledPieces.reset(); break; } // Any pieces that this ride entry supports must be taken out of the array. auto supportedPieces = RideEntryGetSupportedTrackPieces(*currentRideEntry); disabledPieces &= supportedPieces.flip(); } } UpdateDisabledRidePieces(disabledPieces); } /** * * rct2: 0x006CB481 */ WindowBase* RideConstructionOpen() { RideId rideIndex = _currentRideIndex; CloseRideWindowForConstruction(rideIndex); auto currentRide = GetRide(rideIndex); if (currentRide == nullptr) { return nullptr; } WindowRideConstructionUpdateDisabledPieces(currentRide->type); const auto& rtd = currentRide->GetRideTypeDescriptor(); switch (rtd.ConstructionWindowContext) { case RideConstructionWindowContext::Maze: return ContextOpenWindowView(WV_MAZE_CONSTRUCTION); case RideConstructionWindowContext::Default: return WindowCreate( WindowClass::RideConstruction, ScreenCoordsXY(0, 29), WW, WH, WF_NO_AUTO_CLOSE); } return WindowCreate( WindowClass::RideConstruction, ScreenCoordsXY(0, 29), WW, WH, WF_NO_AUTO_CLOSE); } static void CloseConstructWindowOnCompletion(const Ride& ride) { if (_rideConstructionState == RideConstructionState::State0) { auto w = WindowFindByClass(WindowClass::RideConstruction); if (w != nullptr) { if (RideAreAllPossibleEntrancesAndExitsBuilt(ride).Successful) { WindowClose(*w); } else { w->OnMouseUp(WIDX_ENTRANCE); } } } } static void WindowRideConstructionDoEntranceExitCheck() { auto w = WindowFindByClass(WindowClass::RideConstruction); auto ride = GetRide(_currentRideIndex); if (w == nullptr || ride == nullptr) { return; } if (_rideConstructionState == RideConstructionState::State0) { w = WindowFindByClass(WindowClass::RideConstruction); if (w != nullptr) { if (!RideAreAllPossibleEntrancesAndExitsBuilt(*ride).Successful) { w->OnMouseUp(WC_RIDE_CONSTRUCTION__WIDX_ENTRANCE); } } } } static void RideConstructPlacedForwardGameActionCallback(const GameAction* ga, const GameActions::Result* result) { if (result->Error != GameActions::Status::Ok) { WindowRideConstructionUpdateActiveElements(); return; } auto ride = GetRide(_currentRideIndex); if (ride != nullptr) { int32_t trackDirection = _currentTrackPieceDirection; auto trackPos = _currentTrackBegin; if (!(trackDirection & 4)) { trackPos -= CoordsDirectionDelta[trackDirection]; } CoordsXYE next_track; if (TrackBlockGetNextFromZero(trackPos, *ride, trackDirection, &next_track, &trackPos.z, &trackDirection, false)) { _currentTrackBegin.x = next_track.x; _currentTrackBegin.y = next_track.y; _currentTrackBegin.z = trackPos.z; _currentTrackPieceDirection = next_track.element->GetDirection(); _currentTrackPieceType = next_track.element->AsTrack()->GetTrackType(); _currentTrackSelectionFlags = 0; _rideConstructionState = RideConstructionState::Selected; _rideConstructionNextArrowPulse = 0; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; RideSelectNextSection(); } else { _rideConstructionState = RideConstructionState::State0; } WindowRideConstructionDoEntranceExitCheck(); WindowRideConstructionUpdateActiveElements(); } WindowCloseByClass(WindowClass::Error); if (ride != nullptr) CloseConstructWindowOnCompletion(*ride); } static void RideConstructPlacedBackwardGameActionCallback(const GameAction* ga, const GameActions::Result* result) { if (result->Error != GameActions::Status::Ok) { WindowRideConstructionUpdateActiveElements(); return; } auto ride = GetRide(_currentRideIndex); if (ride != nullptr) { auto trackDirection = DirectionReverse(_currentTrackPieceDirection); auto trackPos = _currentTrackBegin; if (!(trackDirection & 4)) { trackPos += CoordsDirectionDelta[trackDirection]; } TrackBeginEnd trackBeginEnd; if (TrackBlockGetPreviousFromZero(trackPos, *ride, trackDirection, &trackBeginEnd)) { _currentTrackBegin.x = trackBeginEnd.begin_x; _currentTrackBegin.y = trackBeginEnd.begin_y; _currentTrackBegin.z = trackBeginEnd.begin_z; _currentTrackPieceDirection = trackBeginEnd.begin_direction; _currentTrackPieceType = trackBeginEnd.begin_element->AsTrack()->GetTrackType(); _currentTrackSelectionFlags = 0; _rideConstructionState = RideConstructionState::Selected; _rideConstructionNextArrowPulse = 0; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; RideSelectPreviousSection(); } else { _rideConstructionState = RideConstructionState::State0; } WindowRideConstructionUpdateActiveElements(); } WindowCloseByClass(WindowClass::Error); if (ride != nullptr) CloseConstructWindowOnCompletion(*ride); } /** * * rct2: 0x006CC538 */ static std::optional RideGetPlacePositionFromScreenPosition(ScreenCoordsXY screenCoords) { CoordsXY mapCoords; if (!_trackPlaceCtrlState) { if (gInputPlaceObjectModifier & PLACE_OBJECT_MODIFIER_COPY_Z) { auto info = GetMapCoordinatesFromPos(screenCoords, 0xFCCA); if (info.SpriteType != ViewportInteractionItem::None) { _trackPlaceCtrlZ = info.Element->GetBaseZ(); _trackPlaceCtrlState = true; } } } else { if (!(gInputPlaceObjectModifier & PLACE_OBJECT_MODIFIER_COPY_Z)) { _trackPlaceCtrlState = false; } } if (!_trackPlaceShiftState) { if (gInputPlaceObjectModifier & PLACE_OBJECT_MODIFIER_SHIFT_Z) { _trackPlaceShiftState = true; _trackPlaceShiftStart = screenCoords; _trackPlaceShiftZ = 0; } } else { if (gInputPlaceObjectModifier & PLACE_OBJECT_MODIFIER_SHIFT_Z) { uint16_t maxHeight = ZoomLevel::max().ApplyTo( std::numeric_limits::max() - 32); _trackPlaceShiftZ = _trackPlaceShiftStart.y - screenCoords.y + 4; // Scale delta by zoom to match mouse position. auto* mainWnd = WindowGetMain(); if (mainWnd != nullptr && mainWnd->viewport != nullptr) { _trackPlaceShiftZ = mainWnd->viewport->zoom.ApplyTo(_trackPlaceShiftZ); } _trackPlaceShiftZ = Floor2(_trackPlaceShiftZ, 8); // Clamp to maximum possible value of BaseHeight can offer. _trackPlaceShiftZ = std::min(_trackPlaceShiftZ, maxHeight); screenCoords = _trackPlaceShiftStart; } else { _trackPlaceShiftState = false; } } if (!_trackPlaceCtrlState) { mapCoords = ViewportInteractionGetTileStartAtCursor(screenCoords); if (mapCoords.IsNull()) return std::nullopt; _trackPlaceZ = 0; if (_trackPlaceShiftState) { auto surfaceElement = MapGetSurfaceElementAt(mapCoords); if (surfaceElement == nullptr) return std::nullopt; auto mapZ = Floor2(surfaceElement->GetBaseZ(), 16); mapZ += _trackPlaceShiftZ; mapZ = std::max(mapZ, 16); _trackPlaceZ = mapZ; } } else { auto mapZ = _trackPlaceCtrlZ; auto mapXYCoords = ScreenGetMapXYWithZ(screenCoords, mapZ); if (mapXYCoords.has_value()) { mapCoords = mapXYCoords.value(); } else { return std::nullopt; } if (_trackPlaceShiftState != 0) { mapZ += _trackPlaceShiftZ; } _trackPlaceZ = std::max(mapZ, 16); } if (mapCoords.x == LOCATION_NULL) return std::nullopt; return mapCoords.ToTileStart(); } /** * * rct2: 0x006C84CE */ void WindowRideConstructionUpdateActiveElementsImpl() { WindowRideConstructionUpdateEnabledTrackPieces(); if (auto currentRide = GetRide(_currentRideIndex); !currentRide || currentRide->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_MAZE)) { return; } auto window = static_cast(WindowFindByClass(WindowClass::RideConstruction)); if (!window) { return; } window->UpdateMapSelection(); _selectedTrackType = TrackElemType::None; if (_rideConstructionState == RideConstructionState::Selected) { TileElement* tileElement; if (GetTrackElementOriginAndApplyChanges( { _currentTrackBegin, static_cast(_currentTrackPieceDirection & 3) }, _currentTrackPieceType, 0, &tileElement, 0) != std::nullopt) { _selectedTrackType = tileElement->AsTrack()->GetTrackType(); if (TrackTypeHasSpeedSetting(tileElement->AsTrack()->GetTrackType())) _currentBrakeSpeed2 = tileElement->AsTrack()->GetBrakeBoosterSpeed(); _currentSeatRotationAngle = tileElement->AsTrack()->GetSeatRotation(); } } window->UpdatePossibleRideConfigurations(); window->UpdateWidgets(); } /** * * rct2: 0x006C6A77 */ void WindowRideConstructionUpdateEnabledTrackPieces() { auto ride = GetRide(_currentRideIndex); if (ride == nullptr) return; auto rideEntry = ride->GetRideEntry(); if (rideEntry == nullptr) return; auto trackDrawerDescriptor = getCurrentTrackDrawerDescriptor(ride->GetRideTypeDescriptor()); UpdateEnabledRidePieces(trackDrawerDescriptor); } /** * * rct2: 0x006C94D8 */ void UpdateGhostTrackAndArrow() { RideId rideIndex; int32_t direction, type, liftHillAndAlternativeState; CoordsXYZ trackPos{}; if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_TRACK_PLACE_ACTION_QUEUED) { return; } // Recheck if area is fine for new track. // Set by footpath placement if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_RECHECK) { RideConstructionInvalidateCurrentTrack(); _currentTrackSelectionFlags &= ~TRACK_SELECTION_FLAG_RECHECK; } switch (_rideConstructionState) { case RideConstructionState::Front: case RideConstructionState::Back: { // place ghost piece if (!(_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_TRACK)) { if (WindowRideConstructionUpdateState( &type, &direction, &rideIndex, &liftHillAndAlternativeState, &trackPos, nullptr)) { RideConstructionRemoveGhosts(); } else { _currentTrackPrice = PlaceProvisionalTrackPiece( rideIndex, type, direction, liftHillAndAlternativeState, trackPos); WindowRideConstructionUpdateActiveElements(); } } // update flashing arrow auto curTime = Platform::GetTicks(); if (_rideConstructionNextArrowPulse >= curTime) break; _rideConstructionNextArrowPulse = curTime + ARROW_PULSE_DURATION; _currentTrackSelectionFlags ^= TRACK_SELECTION_FLAG_ARROW; trackPos = _currentTrackBegin; direction = _currentTrackPieceDirection; type = _currentTrackPieceType; // diagonal pieces trigger this if (direction >= 4) direction += 4; if (_rideConstructionState == RideConstructionState::Back) direction = DirectionReverse(direction); gMapSelectArrowPosition = trackPos; gMapSelectArrowDirection = direction; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_ARROW) gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_ARROW; MapInvalidateTileFull(trackPos); break; } case RideConstructionState::Selected: { auto curTime = Platform::GetTicks(); if (_rideConstructionNextArrowPulse >= curTime) break; _rideConstructionNextArrowPulse = curTime + ARROW_PULSE_DURATION; _currentTrackSelectionFlags ^= TRACK_SELECTION_FLAG_ARROW; direction = _currentTrackPieceDirection & 3; type = _currentTrackPieceType; uint16_t flags = _currentTrackSelectionFlags & TRACK_SELECTION_FLAG_ARROW ? TRACK_ELEMENT_SET_HIGHLIGHT_TRUE : TRACK_ELEMENT_SET_HIGHLIGHT_FALSE; auto newCoords = GetTrackElementOriginAndApplyChanges( { _currentTrackBegin, static_cast(direction) }, type, 0, nullptr, flags); if (!newCoords.has_value()) { RideConstructionRemoveGhosts(); _rideConstructionState = RideConstructionState::State0; } break; } case RideConstructionState::MazeBuild: case RideConstructionState::MazeMove: case RideConstructionState::MazeFill: { auto curTime = Platform::GetTicks(); if (_rideConstructionNextArrowPulse >= curTime) break; _rideConstructionNextArrowPulse = curTime + ARROW_PULSE_DURATION; _currentTrackSelectionFlags ^= TRACK_SELECTION_FLAG_ARROW; trackPos = CoordsXYZ{ _currentTrackBegin.x & 0xFFE0, _currentTrackBegin.y & 0xFFE0, _currentTrackBegin.z + 15 }; gMapSelectArrowPosition = trackPos; gMapSelectArrowDirection = 4; if (((_currentTrackBegin.x & 0x1F) | (_currentTrackBegin.y & 0x1F)) != 0) { gMapSelectArrowDirection = 6; if (((_currentTrackBegin.x & 0x1F) & (_currentTrackBegin.y & 0x1F)) == 0) { gMapSelectArrowDirection = 5; if ((_currentTrackBegin.y & 0x1F) == 0) gMapSelectArrowDirection = 7; } } gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; if (_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_ARROW) gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_ARROW; MapInvalidateTileFull(trackPos); break; } default: break; } } /** * * rct2: 0x006CC6A8 */ void RideConstructionToolupdateConstruct(const ScreenCoordsXY& screenCoords) { int32_t z; const PreviewTrack* trackBlock; MapInvalidateMapSelectionTiles(); gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; auto mapCoords = RideGetPlacePositionFromScreenPosition(screenCoords); if (!mapCoords) { RideConstructionInvalidateCurrentTrack(); MapInvalidateMapSelectionTiles(); return; } z = _trackPlaceZ; if (z == 0) z = MapGetHighestZ(*mapCoords); gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_CONSTRUCT; gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_ARROW; gMapSelectFlags &= ~MAP_SELECT_FLAG_GREEN; gMapSelectArrowPosition = CoordsXYZ{ *mapCoords, z }; gMapSelectArrowDirection = _currentTrackPieceDirection; gMapSelectionTiles.clear(); gMapSelectionTiles.push_back(*mapCoords); RideId rideIndex; int32_t trackType, trackDirection, liftHillAndAlternativeState; if (WindowRideConstructionUpdateState( &trackType, &trackDirection, &rideIndex, &liftHillAndAlternativeState, nullptr, nullptr)) { RideConstructionInvalidateCurrentTrack(); MapInvalidateMapSelectionTiles(); return; } _currentTrackPieceType = trackType; auto ride = GetRide(_currentRideIndex); if (!ride) { return; } const auto& rtd = ride->GetRideTypeDescriptor(); if (!rtd.HasFlag(RIDE_TYPE_FLAG_IS_MAZE)) { auto window = static_cast(WindowFindByClass(WindowClass::RideConstruction)); if (!window) { return; } // Re-using this other code, very slight difference from original // - Original code checks for MSB mask instead of 255 on trackPart->var_00 // - Original code checks this first as its already set origin tile, probably just a micro optimisation window->SelectMapTiles(trackType, trackDirection, *mapCoords); } gMapSelectArrowPosition.z = z; if (_trackPlaceZ == 0) { // Raise z above all slopes and water if (gMapSelectFlags & MAP_SELECT_FLAG_ENABLE_CONSTRUCT) { int32_t highestZ = 0; for (const auto& selectedTile : gMapSelectionTiles) { if (MapIsLocationValid(selectedTile)) { z = MapGetHighestZ(selectedTile); if (z > highestZ) highestZ = z; } } } // Loc6CC8BF: // z = MapGetHighestZ(x >> 5, y >> 5); } // Loc6CC91B: const auto& ted = GetTrackElementDescriptor(trackType); trackBlock = ted.Block; int32_t bx = 0; do { bx = std::min(bx, trackBlock->z); trackBlock++; } while (trackBlock->index != 255); z -= bx; gMapSelectArrowPosition.z = z; _currentTrackBegin.x = mapCoords->x; _currentTrackBegin.y = mapCoords->y; _currentTrackBegin.z = z; if ((_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_TRACK) && _currentTrackBegin == _previousTrackPiece) { MapInvalidateMapSelectionTiles(); return; } _previousTrackPiece = _currentTrackBegin; // search for appropriate z value for ghost, up to max ride height int numAttempts = (z <= MAX_TRACK_HEIGHT ? ((MAX_TRACK_HEIGHT - z) / COORDS_Z_STEP + 1) : 2); if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_MAZE)) { for (int zAttempts = 0; zAttempts < numAttempts; ++zAttempts) { CoordsXYZ trackPos{}; WindowRideConstructionUpdateState( &trackType, &trackDirection, &rideIndex, &liftHillAndAlternativeState, &trackPos, nullptr); _currentTrackPrice = PlaceProvisionalTrackPiece( rideIndex, trackType, trackDirection, liftHillAndAlternativeState, trackPos); if (_currentTrackPrice != kMoney64Undefined) break; _currentTrackBegin.z -= 8; if (_currentTrackBegin.z < 0) break; _currentTrackBegin.z += 16; } auto intent = Intent(INTENT_ACTION_UPDATE_MAZE_CONSTRUCTION); ContextBroadcastIntent(&intent); MapInvalidateMapSelectionTiles(); return; } for (int zAttempts = 0; zAttempts < numAttempts; ++zAttempts) { CoordsXYZ trackPos{}; WindowRideConstructionUpdateState( &trackType, &trackDirection, &rideIndex, &liftHillAndAlternativeState, &trackPos, nullptr); _currentTrackPrice = PlaceProvisionalTrackPiece( rideIndex, trackType, trackDirection, liftHillAndAlternativeState, trackPos); mapCoords = trackPos; z = trackPos.z; if (_currentTrackPrice != kMoney64Undefined) break; _currentTrackBegin.z -= 8; if (_currentTrackBegin.z < 0) break; _currentTrackBegin.z += 16; } if (_autoRotatingShop && _rideConstructionState == RideConstructionState::Place && ride->GetRideTypeDescriptor().HasFlag(RIDE_TYPE_FLAG_IS_SHOP_OR_FACILITY)) { PathElement* pathsByDir[NumOrthogonalDirections]; bool keepOrientation = false; for (int8_t i = 0; i < NumOrthogonalDirections; i++) { const auto testLoc = CoordsXYZ{ *mapCoords + CoordsDirectionDelta[i], z }; if (!MapIsLocationOwned(testLoc)) { pathsByDir[i] = nullptr; continue; } pathsByDir[i] = MapGetFootpathElement(testLoc); if (pathsByDir[i] != nullptr && pathsByDir[i]->IsSloped() && pathsByDir[i]->GetSlopeDirection() != i) { pathsByDir[i] = nullptr; } // Sloped path on the level below if (pathsByDir[i] == nullptr) { pathsByDir[i] = MapGetFootpathElement({ *mapCoords + CoordsDirectionDelta[i], z - PATH_HEIGHT_STEP }); if (pathsByDir[i] != nullptr && (!pathsByDir[i]->IsSloped() || pathsByDir[i]->GetSlopeDirection() != DirectionReverse(i))) { pathsByDir[i] = nullptr; } } if (pathsByDir[i] != nullptr && pathsByDir[i]->IsQueue()) { pathsByDir[i] = nullptr; } if (pathsByDir[i] != nullptr && i == _currentTrackPieceDirection) { keepOrientation = true; break; } } if (!keepOrientation) { for (int8_t i = 0; i < NumOrthogonalDirections; i++) { if (pathsByDir[i] != nullptr) { _currentTrackPieceDirection = i; CoordsXYZ trackPos{}; WindowRideConstructionUpdateState( &trackType, &trackDirection, &rideIndex, &liftHillAndAlternativeState, &trackPos, nullptr); PlaceProvisionalTrackPiece(rideIndex, trackType, trackDirection, liftHillAndAlternativeState, trackPos); gMapSelectArrowDirection = _currentTrackPieceDirection; break; } } } } WindowRideConstructionUpdateActiveElements(); MapInvalidateMapSelectionTiles(); } /** * * rct2: 0x006CD354 */ void RideConstructionToolupdateEntranceExit(const ScreenCoordsXY& screenCoords) { MapInvalidateSelectionRect(); MapInvalidateMapSelectionTiles(); gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; CoordsXYZD entranceOrExitCoords = RideGetEntranceOrExitPositionFromScreenPosition(screenCoords); if (gRideEntranceExitPlaceDirection == INVALID_DIRECTION) { RideConstructionInvalidateCurrentTrack(); return; } gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE; gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_ARROW; gMapSelectType = MAP_SELECT_TYPE_FULL; gMapSelectPositionA = entranceOrExitCoords; gMapSelectPositionB = entranceOrExitCoords; gMapSelectArrowPosition = entranceOrExitCoords; gMapSelectArrowDirection = DirectionReverse(entranceOrExitCoords.direction); MapInvalidateSelectionRect(); entranceOrExitCoords.direction = DirectionReverse(gRideEntranceExitPlaceDirection); StationIndex stationNum = gRideEntranceExitPlaceStationIndex; if (!(_currentTrackSelectionFlags & TRACK_SELECTION_FLAG_ENTRANCE_OR_EXIT) || entranceOrExitCoords != gRideEntranceExitGhostPosition || stationNum != gRideEntranceExitGhostStationIndex) { auto ride = GetRide(_currentRideIndex); if (ride != nullptr) { _currentTrackPrice = RideEntranceExitPlaceGhost( *ride, entranceOrExitCoords, entranceOrExitCoords.direction, gRideEntranceExitPlaceType, stationNum); } WindowRideConstructionUpdateActiveElements(); } } /** * * rct2: 0x006CCA73 */ void RideConstructionTooldownConstruct(const ScreenCoordsXY& screenCoords) { const CursorState* state = ContextGetCursorState(); WindowBase* w; MapInvalidateMapSelectionTiles(); RideConstructionInvalidateCurrentTrack(); CoordsXYZ mapCoords{}; int32_t trackType, z, highestZ; if (WindowRideConstructionUpdateState(&trackType, nullptr, nullptr, nullptr, nullptr, nullptr)) return; z = mapCoords.z; _currentTrackPieceType = trackType; // Raise z above all slopes and water highestZ = 0; if (gMapSelectFlags & MAP_SELECT_FLAG_ENABLE_CONSTRUCT) { for (const auto& selectedTile : gMapSelectionTiles) { if (!MapIsLocationValid(selectedTile)) continue; z = MapGetHighestZ(selectedTile); if (z > highestZ) highestZ = z; } } gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; auto ridePlacePosition = RideGetPlacePositionFromScreenPosition(screenCoords); if (!ridePlacePosition) return; mapCoords = { *ridePlacePosition, z }; z = _trackPlaceZ; if (z == 0) z = MapGetHighestZ(mapCoords); ToolCancel(); auto ride = GetRide(_currentRideIndex); if (ride == nullptr) return; if (_trackPlaceZ == 0) { const auto& ted = GetTrackElementDescriptor(_currentTrackPieceType); const PreviewTrack* trackBlock = ted.Block; int32_t bx = 0; do { bx = std::min(bx, trackBlock->z); trackBlock++; } while (trackBlock->index != 255); z -= bx; // FIX not sure exactly why it starts trial and error place from a lower Z, but it causes issues with disable // clearance if (!GetGameState().Cheats.DisableClearanceChecks && z > kMinimumLandZ) { z -= LAND_HEIGHT_STEP; } } else { z = _trackPlaceZ; } // search for z value to build at, up to max ride height int numAttempts = (z <= MAX_TRACK_HEIGHT ? ((MAX_TRACK_HEIGHT - z) / COORDS_Z_STEP + 1) : 2); const auto& rtd = ride->GetRideTypeDescriptor(); if (rtd.HasFlag(RIDE_TYPE_FLAG_IS_MAZE)) { for (int32_t zAttempts = 0; zAttempts < numAttempts; ++zAttempts) { _rideConstructionState = RideConstructionState::MazeBuild; _currentTrackBegin.x = mapCoords.x; _currentTrackBegin.y = mapCoords.y; _currentTrackBegin.z = z; _currentTrackSelectionFlags = 0; auto intent = Intent(INTENT_ACTION_UPDATE_MAZE_CONSTRUCTION); ContextBroadcastIntent(&intent); w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr) break; gDisableErrorWindowSound = true; auto gameAction = MazeSetTrackAction( CoordsXYZD{ _currentTrackBegin, 0 }, true, _currentRideIndex, GC_SET_MAZE_TRACK_BUILD); auto mazeSetTrackResult = GameActions::Execute(&gameAction); if (mazeSetTrackResult.Error == GameActions::Status::Ok) { _trackPlaceCost = mazeSetTrackResult.Cost; _trackPlaceErrorMessage = STR_NONE; } else { _trackPlaceCost = kMoney64Undefined; _trackPlaceErrorMessage = std::get(mazeSetTrackResult.ErrorMessage); } gDisableErrorWindowSound = false; if (mazeSetTrackResult.Error != GameActions::Status::Ok) { _rideConstructionState = RideConstructionState::Place; StringId errorText = std::get(mazeSetTrackResult.ErrorMessage); z -= 8; if (errorText == STR_NOT_ENOUGH_CASH_REQUIRES || errorText == STR_CAN_ONLY_BUILD_THIS_UNDERWATER || errorText == STR_CAN_ONLY_BUILD_THIS_ON_WATER || errorText == STR_RIDE_CANT_BUILD_THIS_UNDERWATER || errorText == STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND || errorText == STR_TOO_HIGH_FOR_SUPPORTS || zAttempts == (numAttempts - 1) || z < 0) { OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Error, 0, state->position.x); w = WindowFindByClass(WindowClass::RideConstruction); if (w != nullptr) { ToolSet(*w, WIDX_CONSTRUCT, Tool::Crosshair); InputSetFlag(INPUT_FLAG_6, true); _trackPlaceCtrlState = false; _trackPlaceShiftState = false; } auto intent2 = Intent(INTENT_ACTION_UPDATE_MAZE_CONSTRUCTION); ContextBroadcastIntent(&intent2); break; } z += 16; } else { WindowCloseByClass(WindowClass::Error); OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::PlaceItem, _currentTrackBegin); break; } } return; } for (int32_t zAttempts = 0; zAttempts < numAttempts; ++zAttempts) { _rideConstructionState = RideConstructionState::Front; _currentTrackBegin.x = mapCoords.x; _currentTrackBegin.y = mapCoords.y; _currentTrackBegin.z = z; _currentTrackSelectionFlags = 0; WindowRideConstructionUpdateActiveElements(); w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr) break; gDisableErrorWindowSound = true; w->OnMouseUp(WIDX_CONSTRUCT); gDisableErrorWindowSound = false; if (_trackPlaceCost == kMoney64Undefined) { StringId errorText = _trackPlaceErrorMessage; z -= 8; if (errorText == STR_NOT_ENOUGH_CASH_REQUIRES || errorText == STR_CAN_ONLY_BUILD_THIS_UNDERWATER || errorText == STR_CAN_ONLY_BUILD_THIS_ON_WATER || errorText == STR_CAN_ONLY_BUILD_THIS_ABOVE_GROUND || errorText == STR_TOO_HIGH_FOR_SUPPORTS || errorText == STR_TOO_HIGH || errorText == STR_LOCAL_AUTHORITY_WONT_ALLOW_CONSTRUCTION_ABOVE_TREE_HEIGHT || zAttempts == (numAttempts - 1) || z < 0) { int32_t saveTrackDirection = _currentTrackPieceDirection; auto saveCurrentTrackCurve = _currentTrackCurve; auto savePreviousTrackPitchEnd = _previousTrackPitchEnd; auto saveCurrentTrackPitchEnd = _currentTrackPitchEnd; auto savePreviousTrackRollEnd = _previousTrackRollEnd; auto saveCurrentTrackRollEnd = _currentTrackRollEnd; int32_t saveCurrentTrackAlternative = _currentTrackAlternative; int32_t saveCurrentTrackLiftHill = _currentTrackLiftHill; RideInitialiseConstructionWindow(*ride); _currentTrackPieceDirection = saveTrackDirection; _currentTrackCurve = saveCurrentTrackCurve; _previousTrackPitchEnd = savePreviousTrackPitchEnd; _currentTrackPitchEnd = saveCurrentTrackPitchEnd; _previousTrackRollEnd = savePreviousTrackRollEnd; _currentTrackRollEnd = saveCurrentTrackRollEnd; _currentTrackAlternative = saveCurrentTrackAlternative; _currentTrackLiftHill = saveCurrentTrackLiftHill; OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Error, 0, state->position.x); break; } z += 16; } else { break; } } } void WindowRideConstructionKeyboardShortcutTurnLeft() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_STRAIGHT) || w->widgets[WIDX_STRAIGHT].type == WindowWidgetType::Empty) { return; } switch (_currentTrackCurve) { case EnumValue(TrackCurve::LeftSmall): if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE_VERY_SMALL) && w->widgets[WIDX_LEFT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_VERY_SMALL); } break; case EnumValue(TrackCurve::Left): if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE_SMALL) && w->widgets[WIDX_LEFT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_VERY_SMALL) && w->widgets[WIDX_LEFT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::LeftLarge): if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE) && w->widgets[WIDX_LEFT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_SMALL) && w->widgets[WIDX_LEFT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_VERY_SMALL) && w->widgets[WIDX_LEFT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::None): if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE_LARGE) && w->widgets[WIDX_LEFT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE) && w->widgets[WIDX_LEFT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_SMALL) && w->widgets[WIDX_LEFT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_VERY_SMALL) && w->widgets[WIDX_LEFT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::RightLarge): if (!WidgetIsDisabled(*w, WIDX_STRAIGHT) && w->widgets[WIDX_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_STRAIGHT); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_LARGE) && w->widgets[WIDX_LEFT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE) && w->widgets[WIDX_LEFT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_SMALL) && w->widgets[WIDX_LEFT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_VERY_SMALL) && w->widgets[WIDX_LEFT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::Right): if (!WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_LARGE) && w->widgets[WIDX_RIGHT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_STRAIGHT) && w->widgets[WIDX_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_STRAIGHT); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_LARGE) && w->widgets[WIDX_LEFT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE) && w->widgets[WIDX_LEFT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_SMALL) && w->widgets[WIDX_LEFT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_VERY_SMALL) && w->widgets[WIDX_LEFT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::RightSmall): if (!WidgetIsDisabled(*w, WIDX_RIGHT_CURVE) && w->widgets[WIDX_RIGHT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_LARGE) && w->widgets[WIDX_RIGHT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_STRAIGHT) && w->widgets[WIDX_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_STRAIGHT); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_LARGE) && w->widgets[WIDX_LEFT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE) && w->widgets[WIDX_LEFT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_SMALL) && w->widgets[WIDX_LEFT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_VERY_SMALL) && w->widgets[WIDX_LEFT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::RightVerySmall): if (!WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_SMALL) && w->widgets[WIDX_RIGHT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE) && w->widgets[WIDX_RIGHT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_LARGE) && w->widgets[WIDX_RIGHT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_STRAIGHT) && w->widgets[WIDX_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_STRAIGHT); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_LARGE) && w->widgets[WIDX_LEFT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE) && w->widgets[WIDX_LEFT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_SMALL) && w->widgets[WIDX_LEFT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_VERY_SMALL) && w->widgets[WIDX_LEFT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_VERY_SMALL); } else { return; } break; default: return; } } void WindowRideConstructionKeyboardShortcutTurnRight() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_STRAIGHT) || w->widgets[WIDX_STRAIGHT].type == WindowWidgetType::Empty) { return; } switch (_currentTrackCurve) { case EnumValue(TrackCurve::RightSmall): if (!WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_VERY_SMALL) && w->widgets[WIDX_RIGHT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_VERY_SMALL); } break; case EnumValue(TrackCurve::Right): if (!WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_SMALL) && w->widgets[WIDX_RIGHT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_VERY_SMALL) && w->widgets[WIDX_RIGHT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::RightLarge): if (!WidgetIsDisabled(*w, WIDX_RIGHT_CURVE) && w->widgets[WIDX_RIGHT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_SMALL) && w->widgets[WIDX_RIGHT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_VERY_SMALL) && w->widgets[WIDX_RIGHT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::None): if (!WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_LARGE) && w->widgets[WIDX_RIGHT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE) && w->widgets[WIDX_RIGHT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_SMALL) && w->widgets[WIDX_RIGHT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_VERY_SMALL) && w->widgets[WIDX_RIGHT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::LeftLarge): if (!WidgetIsDisabled(*w, WIDX_STRAIGHT) && w->widgets[WIDX_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_STRAIGHT); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_LARGE) && w->widgets[WIDX_RIGHT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_LARGE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE) && w->widgets[WIDX_RIGHT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_SMALL) && w->widgets[WIDX_RIGHT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_VERY_SMALL) && w->widgets[WIDX_RIGHT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::Left): if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE_LARGE) && w->widgets[WIDX_LEFT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_STRAIGHT) && w->widgets[WIDX_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_STRAIGHT); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_LARGE) && w->widgets[WIDX_RIGHT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_LARGE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE) && w->widgets[WIDX_RIGHT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_SMALL) && w->widgets[WIDX_RIGHT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_VERY_SMALL) && w->widgets[WIDX_RIGHT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::LeftSmall): if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE) && w->widgets[WIDX_LEFT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_LARGE) && w->widgets[WIDX_LEFT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_STRAIGHT) && w->widgets[WIDX_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_STRAIGHT); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_LARGE) && w->widgets[WIDX_RIGHT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_LARGE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE) && w->widgets[WIDX_RIGHT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_SMALL) && w->widgets[WIDX_RIGHT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_VERY_SMALL) && w->widgets[WIDX_RIGHT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_VERY_SMALL); } else { return; } break; case EnumValue(TrackCurve::LeftVerySmall): if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE_SMALL) && w->widgets[WIDX_LEFT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_SMALL); } else if (!WidgetIsDisabled(*w, WIDX_LEFT_CURVE) && w->widgets[WIDX_LEFT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_LEFT_CURVE_LARGE) && w->widgets[WIDX_LEFT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEFT_CURVE_LARGE); } else if (!WidgetIsDisabled(*w, WIDX_STRAIGHT) && w->widgets[WIDX_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_STRAIGHT); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_LARGE) && w->widgets[WIDX_RIGHT_CURVE_LARGE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_LARGE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE) && w->widgets[WIDX_RIGHT_CURVE].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_SMALL) && w->widgets[WIDX_RIGHT_CURVE_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_SMALL); } else if ( !WidgetIsDisabled(*w, WIDX_RIGHT_CURVE_VERY_SMALL) && w->widgets[WIDX_RIGHT_CURVE_VERY_SMALL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_RIGHT_CURVE_VERY_SMALL); } else { return; } break; default: return; } } void WindowRideConstructionKeyboardShortcutUseTrackDefault() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_STRAIGHT) || w->widgets[WIDX_STRAIGHT].type == WindowWidgetType::Empty) { return; } if (!WidgetIsDisabled(*w, WIDX_STRAIGHT) && w->widgets[WIDX_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_STRAIGHT); } if (!WidgetIsDisabled(*w, WIDX_LEVEL) && w->widgets[WIDX_LEVEL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEVEL); } if (!WidgetIsDisabled(*w, WIDX_CHAIN_LIFT) && w->widgets[WIDX_CHAIN_LIFT].type != WindowWidgetType::Empty && _currentTrackLiftHill & CONSTRUCTION_LIFT_HILL_SELECTED) { w->OnMouseDown(WIDX_CHAIN_LIFT); } if (!WidgetIsDisabled(*w, WIDX_BANK_STRAIGHT) && w->widgets[WIDX_BANK_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_BANK_STRAIGHT); } } void WindowRideConstructionKeyboardShortcutSlopeDown() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_STRAIGHT) || w->widgets[WIDX_STRAIGHT].type == WindowWidgetType::Empty) { return; } switch (_currentTrackPitchEnd) { case TrackPitch::Down60: if (IsTrackEnabled(TRACK_SLOPE_VERTICAL) && !WidgetIsDisabled(*w, WIDX_SLOPE_UP_STEEP) && w->widgets[WIDX_SLOPE_UP_STEEP].image.GetIndex() == SPR_RIDE_CONSTRUCTION_VERTICAL_DROP && w->widgets[WIDX_SLOPE_UP_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_UP_STEEP); } break; case TrackPitch::Down25: if (!WidgetIsDisabled(*w, WIDX_SLOPE_DOWN_STEEP) && w->widgets[WIDX_SLOPE_DOWN_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_DOWN_STEEP); } break; case TrackPitch::None: if (!WidgetIsDisabled(*w, WIDX_SLOPE_DOWN) && w->widgets[WIDX_SLOPE_DOWN].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_DOWN); } else if ( IsTrackEnabled(TRACK_SLOPE_VERTICAL) && w->widgets[WIDX_SLOPE_DOWN_STEEP].image.GetIndex() == SPR_RIDE_CONSTRUCTION_VERTICAL_RISE) { return; } else if ( !WidgetIsDisabled(*w, WIDX_SLOPE_DOWN_STEEP) && w->widgets[WIDX_SLOPE_DOWN_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_DOWN_STEEP); } else { return; } break; case TrackPitch::Up25: if (!WidgetIsDisabled(*w, WIDX_LEVEL) && w->widgets[WIDX_LEVEL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEVEL); } else if (!WidgetIsDisabled(*w, WIDX_SLOPE_DOWN) && w->widgets[WIDX_SLOPE_DOWN].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_DOWN); } else if ( !WidgetIsDisabled(*w, WIDX_SLOPE_DOWN_STEEP) && w->widgets[WIDX_SLOPE_DOWN_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_DOWN_STEEP); } else { return; } break; case TrackPitch::Up60: if (!WidgetIsDisabled(*w, WIDX_SLOPE_UP) && w->widgets[WIDX_SLOPE_UP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_UP); } else if (!WidgetIsDisabled(*w, WIDX_LEVEL) && w->widgets[WIDX_LEVEL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEVEL); } else if (!WidgetIsDisabled(*w, WIDX_SLOPE_DOWN) && w->widgets[WIDX_SLOPE_DOWN].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_DOWN); } else if ( IsTrackEnabled(TRACK_SLOPE_VERTICAL) && w->widgets[WIDX_SLOPE_DOWN_STEEP].image.GetIndex() == SPR_RIDE_CONSTRUCTION_VERTICAL_RISE) { return; } else if ( !WidgetIsDisabled(*w, WIDX_SLOPE_DOWN_STEEP) && w->widgets[WIDX_SLOPE_DOWN_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_DOWN_STEEP); } else { return; } break; case TrackPitch::Up90: if (IsTrackEnabled(TRACK_SLOPE_VERTICAL) && !WidgetIsDisabled(*w, WIDX_SLOPE_UP_STEEP) && w->widgets[WIDX_SLOPE_DOWN_STEEP].image.GetIndex() == SPR_RIDE_CONSTRUCTION_VERTICAL_RISE && w->widgets[WIDX_SLOPE_DOWN_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_UP_STEEP); } break; default: return; } } void WindowRideConstructionKeyboardShortcutSlopeUp() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_STRAIGHT) || w->widgets[WIDX_STRAIGHT].type == WindowWidgetType::Empty) { return; } switch (_currentTrackPitchEnd) { case TrackPitch::Up60: if (IsTrackEnabled(TRACK_SLOPE_VERTICAL) && !WidgetIsDisabled(*w, WIDX_SLOPE_DOWN_STEEP) && w->widgets[WIDX_SLOPE_DOWN_STEEP].image.GetIndex() == SPR_RIDE_CONSTRUCTION_VERTICAL_RISE && w->widgets[WIDX_SLOPE_DOWN_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_DOWN_STEEP); } break; case TrackPitch::Up25: if (!WidgetIsDisabled(*w, WIDX_SLOPE_UP_STEEP) && w->widgets[WIDX_SLOPE_UP_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_UP_STEEP); } break; case TrackPitch::None: if (!WidgetIsDisabled(*w, WIDX_SLOPE_UP) && w->widgets[WIDX_SLOPE_UP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_UP); } else if ( IsTrackEnabled(TRACK_SLOPE_VERTICAL) && w->widgets[WIDX_SLOPE_UP_STEEP].image.GetIndex() == SPR_RIDE_CONSTRUCTION_VERTICAL_DROP) { return; } else if ( !WidgetIsDisabled(*w, WIDX_SLOPE_UP_STEEP) && w->widgets[WIDX_SLOPE_UP_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_UP_STEEP); } else { return; } break; case TrackPitch::Down25: if (!WidgetIsDisabled(*w, WIDX_LEVEL) && w->widgets[WIDX_LEVEL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEVEL); } else if (!WidgetIsDisabled(*w, WIDX_SLOPE_UP) && w->widgets[WIDX_SLOPE_UP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_UP); } else if ( !WidgetIsDisabled(*w, WIDX_SLOPE_UP_STEEP) && w->widgets[WIDX_SLOPE_UP_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_UP_STEEP); } else { return; } break; case TrackPitch::Down60: if (!WidgetIsDisabled(*w, WIDX_SLOPE_DOWN) && w->widgets[WIDX_SLOPE_DOWN].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_DOWN); } else if (!WidgetIsDisabled(*w, WIDX_LEVEL) && w->widgets[WIDX_LEVEL].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_LEVEL); } else if (!WidgetIsDisabled(*w, WIDX_SLOPE_UP) && w->widgets[WIDX_SLOPE_UP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_UP); } else if ( IsTrackEnabled(TRACK_SLOPE_VERTICAL) && w->widgets[WIDX_SLOPE_UP_STEEP].image.GetIndex() == SPR_RIDE_CONSTRUCTION_VERTICAL_DROP) { return; } else if ( !WidgetIsDisabled(*w, WIDX_SLOPE_UP_STEEP) && w->widgets[WIDX_SLOPE_UP_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_UP_STEEP); } else { return; } break; case TrackPitch::Down90: if (IsTrackEnabled(TRACK_SLOPE_VERTICAL) && !WidgetIsDisabled(*w, WIDX_SLOPE_DOWN_STEEP) && w->widgets[WIDX_SLOPE_UP_STEEP].image.GetIndex() == SPR_RIDE_CONSTRUCTION_VERTICAL_DROP && w->widgets[WIDX_SLOPE_DOWN_STEEP].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_SLOPE_DOWN_STEEP); } break; default: return; } } void WindowRideConstructionKeyboardShortcutChainLiftToggle() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_CHAIN_LIFT) || w->widgets[WIDX_CHAIN_LIFT].type == WindowWidgetType::Empty) { return; } w->OnMouseDown(WIDX_CHAIN_LIFT); } void WindowRideConstructionKeyboardShortcutBankLeft() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_BANK_STRAIGHT) || w->widgets[WIDX_BANK_STRAIGHT].type == WindowWidgetType::Empty) { return; } switch (_currentTrackRollEnd) { case TrackRoll::None: if (!WidgetIsDisabled(*w, WIDX_BANK_LEFT) && w->widgets[WIDX_BANK_LEFT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_BANK_LEFT); } break; case TrackRoll::Right: if (!WidgetIsDisabled(*w, WIDX_BANK_STRAIGHT) && w->widgets[WIDX_BANK_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_BANK_STRAIGHT); } else if (!WidgetIsDisabled(*w, WIDX_BANK_LEFT) && w->widgets[WIDX_BANK_LEFT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_BANK_LEFT); } else { return; } break; default: return; } } void WindowRideConstructionKeyboardShortcutBankRight() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_BANK_STRAIGHT) || w->widgets[WIDX_BANK_STRAIGHT].type == WindowWidgetType::Empty) { return; } switch (_currentTrackRollEnd) { case TrackRoll::None: if (!WidgetIsDisabled(*w, WIDX_BANK_RIGHT) && w->widgets[WIDX_BANK_RIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_BANK_RIGHT); } break; case TrackRoll::Left: if (!WidgetIsDisabled(*w, WIDX_BANK_STRAIGHT) && w->widgets[WIDX_BANK_STRAIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_BANK_STRAIGHT); } else if (!WidgetIsDisabled(*w, WIDX_BANK_RIGHT) && w->widgets[WIDX_BANK_RIGHT].type != WindowWidgetType::Empty) { w->OnMouseDown(WIDX_BANK_RIGHT); } else { return; } break; default: return; } } void WindowRideConstructionKeyboardShortcutPreviousTrack() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_PREVIOUS_SECTION) || w->widgets[WIDX_PREVIOUS_SECTION].type == WindowWidgetType::Empty) { return; } w->OnMouseUp(WIDX_PREVIOUS_SECTION); } void WindowRideConstructionKeyboardShortcutNextTrack() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_NEXT_SECTION) || w->widgets[WIDX_NEXT_SECTION].type == WindowWidgetType::Empty) { return; } w->OnMouseUp(WIDX_NEXT_SECTION); } void WindowRideConstructionKeyboardShortcutBuildCurrent() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_CONSTRUCT) || w->widgets[WIDX_CONSTRUCT].type == WindowWidgetType::Empty) { return; } w->OnMouseUp(WIDX_CONSTRUCT); } void WindowRideConstructionKeyboardShortcutDemolishCurrent() { WindowBase* w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr || WidgetIsDisabled(*w, WIDX_DEMOLISH) || w->widgets[WIDX_DEMOLISH].type == WindowWidgetType::Empty) { return; } w->OnMouseUp(WIDX_DEMOLISH); } static void WindowRideConstructionMouseUpDemolishNextPiece(const CoordsXYZD& piecePos, int32_t type) { if (_gotoStartPlacementMode) { _currentTrackBegin.z = Floor2(piecePos.z, COORDS_Z_STEP); _rideConstructionState = RideConstructionState::Front; _currentTrackSelectionFlags = 0; _currentTrackPieceDirection = piecePos.direction & 3; auto savedCurrentTrackCurve = _currentTrackCurve; auto savedPreviousTrackPitchEnd = _previousTrackPitchEnd; auto savedCurrentTrackPitchEnd = _currentTrackPitchEnd; auto savedPreviousTrackRollEnd = _previousTrackRollEnd; auto savedCurrentTrackRollEnd = _currentTrackRollEnd; int32_t savedCurrentTrackAlternative = _currentTrackAlternative; int32_t savedCurrentTrackLiftHill = _currentTrackLiftHill; RideConstructionSetDefaultNextPiece(); WindowRideConstructionUpdateActiveElements(); auto ride = GetRide(_currentRideIndex); if (!RideTryGetOriginElement(*ride, nullptr)) { RideInitialiseConstructionWindow(*ride); _currentTrackPieceDirection = piecePos.direction & 3; if (!(savedCurrentTrackCurve & RideConstructionSpecialPieceSelected)) { _currentTrackCurve = savedCurrentTrackCurve; _previousTrackPitchEnd = savedPreviousTrackPitchEnd; _currentTrackPitchEnd = savedCurrentTrackPitchEnd; _previousTrackRollEnd = savedPreviousTrackRollEnd; _currentTrackRollEnd = savedCurrentTrackRollEnd; _currentTrackAlternative = savedCurrentTrackAlternative; _currentTrackLiftHill = savedCurrentTrackLiftHill; WindowRideConstructionUpdateActiveElements(); } } } else { if (_rideConstructionState2 == RideConstructionState::Selected || _rideConstructionState2 == RideConstructionState::Front) { if (type == TrackElemType::MiddleStation || type == TrackElemType::BeginStation) { type = TrackElemType::EndStation; } } if (_rideConstructionState2 == RideConstructionState::Back) { if (type == TrackElemType::MiddleStation) { type = TrackElemType::BeginStation; } } if (NetworkGetMode() == NETWORK_MODE_CLIENT) { // rideConstructionState needs to be set again to the proper value, this only affects the client _rideConstructionState = RideConstructionState::Selected; } _currentTrackBegin = piecePos; _currentTrackPieceDirection = piecePos.direction; _currentTrackPieceType = type; _currentTrackSelectionFlags = 0; if (_rideConstructionState2 == RideConstructionState::Front) { RideSelectNextSection(); } else if (_rideConstructionState2 == RideConstructionState::Back) { RideSelectPreviousSection(); } WindowRideConstructionUpdateActiveElements(); } } } // namespace OpenRCT2::Ui::Windows