/***************************************************************************** * 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 namespace OpenRCT2::Ui::Windows { #pragma region Widgets static constexpr StringId WINDOW_TITLE = STR_RIDE_CONSTRUCTION_WINDOW_TITLE; static constexpr int32_t WH = 200; static constexpr int32_t WW = 166; enum : WidgetIndex { WIDX_BACKGROUND, WIDX_TITLE, WIDX_CLOSE, WIDX_MAZE_MODE_GROUPBOX, WIDX_MAZE_BUILD_MODE = 6, WIDX_MAZE_MOVE_MODE, WIDX_MAZE_FILL_MODE, WIDX_MAZE_DIRECTION_GROUPBOX = 25, WIDX_MAZE_DIRECTION_NW, WIDX_MAZE_DIRECTION_NE, WIDX_MAZE_DIRECTION_SW, WIDX_MAZE_DIRECTION_SE, WIDX_MAZE_ENTRANCE = 30, WIDX_MAZE_EXIT, }; validate_global_widx(WC_MAZE_CONSTRUCTION, WIDX_MAZE_DIRECTION_GROUPBOX); validate_global_widx(WC_MAZE_CONSTRUCTION, WIDX_MAZE_ENTRANCE); validate_global_widx(WC_MAZE_CONSTRUCTION, WIDX_MAZE_EXIT); // clang-format off static Widget window_maze_construction_widgets[] = { WINDOW_SHIM(WINDOW_TITLE, WW, WH), MakeWidget({ 3, 17}, {160, 55}, WindowWidgetType::Groupbox, WindowColour::Primary , STR_RIDE_CONSTRUCTION_MODE ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({35, 29}, { 32, 32}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_MAZE_CONSTRUCTION_BUILD), STR_RIDE_CONSTRUCTION_BUILD_MODE ), MakeWidget({67, 29}, { 32, 32}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_MAZE_CONSTRUCTION_MOVE), STR_RIDE_CONSTRUCTION_MOVE_MODE ), MakeWidget({99, 29}, { 32, 32}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_MAZE_CONSTRUCTION_FILL_IN), STR_RIDE_CONSTRUCTION_FILL_IN_MODE ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 3, 168}, {160, 28}, WindowWidgetType::Groupbox, WindowColour::Primary ), MakeWidget({ 3, 80}, {160, 87}, WindowWidgetType::Groupbox, WindowColour::Primary , STR_RIDE_CONSTRUCTION_BUILD ), MakeWidget({83, 96}, { 45, 29}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_CONSTRUCTION_DIRECTION_NE), STR_RIDE_CONSTRUCTION_BUILD_MAZE_IN_THIS_DIRECTION_TIP), MakeWidget({83, 125}, { 45, 29}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_CONSTRUCTION_DIRECTION_SE), STR_RIDE_CONSTRUCTION_BUILD_MAZE_IN_THIS_DIRECTION_TIP), MakeWidget({38, 125}, { 45, 29}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_CONSTRUCTION_DIRECTION_SW), STR_RIDE_CONSTRUCTION_BUILD_MAZE_IN_THIS_DIRECTION_TIP), MakeWidget({38, 96}, { 45, 29}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_CONSTRUCTION_DIRECTION_NW), STR_RIDE_CONSTRUCTION_BUILD_MAZE_IN_THIS_DIRECTION_TIP), MakeWidget({ 9, 178}, { 70, 12}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_CONSTRUCTION_ENTRANCE, STR_RIDE_CONSTRUCTION_ENTRANCE_TIP ), MakeWidget({87, 178}, { 70, 12}, WindowWidgetType::Button, WindowColour::Secondary, STR_RIDE_CONSTRUCTION_EXIT, STR_RIDE_CONSTRUCTION_EXIT_TIP ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), MakeWidget({ 0, 0}, { 1, 1}, WindowWidgetType::Empty, WindowColour::Primary ), kWidgetsEnd, }; // clang-format on #pragma endregion class MazeConstructionWindow final : public Window { public: void OnOpen() override { widgets = window_maze_construction_widgets; WindowInitScrollWidgets(*this); rideId = _currentRideIndex; ShowGridlines(); } 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. ToolCancel(); HideGridlines(); auto currentRide = GetRide(_currentRideIndex); if (currentRide != nullptr) { if (currentRide->overall_view.IsNull()) { auto gameAction = RideDemolishAction(currentRide->id, RIDE_MODIFY_DEMOLISH); gameAction.SetFlags(GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED); GameActions::Execute(&gameAction); } else { auto intent = Intent(WindowClass::Ride); intent.PutExtra(INTENT_EXTRA_RIDE_ID, currentRide->id.ToUnderlying()); ContextOpenIntent(&intent); } } } void OnMouseUp(WidgetIndex widgetIndex) override { switch (widgetIndex) { case WIDX_CLOSE: Close(); break; case WIDX_MAZE_ENTRANCE: case WIDX_MAZE_EXIT: WindowMazeConstructionEntranceMouseup(widgetIndex); break; case WIDX_MAZE_DIRECTION_NW: case WIDX_MAZE_DIRECTION_NE: case WIDX_MAZE_DIRECTION_SE: case WIDX_MAZE_DIRECTION_SW: WindowMazeConstructionConstruct(((widgetIndex - WIDX_MAZE_DIRECTION_NW) - GetCurrentRotation()) & 3); break; } } void OnResize() override { ResizeFrameWithPage(); uint64_t disabledWidgets = 0; if (_rideConstructionState == RideConstructionState::Place) { disabledWidgets |= ((1uLL << WIDX_MAZE_BUILD_MODE) | (1uLL << WIDX_MAZE_MOVE_MODE) | (1uLL << WIDX_MAZE_FILL_MODE) | (1uLL << WIDX_MAZE_DIRECTION_NW) | (1uLL << WIDX_MAZE_DIRECTION_NE) | (1uLL << WIDX_MAZE_DIRECTION_SW) | (1uLL << WIDX_MAZE_DIRECTION_SE)); } else if (_rideConstructionState == RideConstructionState::EntranceExit) { disabledWidgets = (1uLL << WIDX_MAZE_DIRECTION_NW) | (1uLL << WIDX_MAZE_DIRECTION_NE) | (1uLL << WIDX_MAZE_DIRECTION_SW) | (1uLL << WIDX_MAZE_DIRECTION_SE); } // 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 OnMouseDown(WidgetIndex widgetIndex) override { switch (widgetIndex) { case WIDX_MAZE_BUILD_MODE: WindowMazeConstructionBuildModeMousedown(RideConstructionState::MazeBuild); break; case WIDX_MAZE_MOVE_MODE: WindowMazeConstructionBuildModeMousedown(RideConstructionState::MazeMove); break; case WIDX_MAZE_FILL_MODE: WindowMazeConstructionBuildModeMousedown(RideConstructionState::MazeFill); break; } } void OnUpdate() override { auto currentRide = GetRide(_currentRideIndex); if (currentRide == nullptr || currentRide->status != RideStatus::Closed) { Close(); return; } switch (_rideConstructionState) { case RideConstructionState::Place: if (!isToolActive(*this, WIDX_MAZE_DIRECTION_GROUPBOX)) { Close(); return; } break; case RideConstructionState::EntranceExit: if (!isToolActive(*this, WIDX_MAZE_ENTRANCE) && !isToolActive(*this, WIDX_MAZE_EXIT)) { _rideConstructionState = gRideEntranceExitPlacePreviousRideConstructionState; WindowMazeConstructionUpdatePressedWidgets(); } break; default: break; } switch (_rideConstructionState) { case RideConstructionState::Front: case RideConstructionState::Back: case RideConstructionState::Selected: if (isToolActive(WindowClass::RideConstruction)) { ToolCancel(); } break; default: break; } UpdateGhostTrackAndArrow(); } void OnToolUpdate(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override { switch (widgetIndex) { case WIDX_MAZE_DIRECTION_GROUPBOX: RideConstructionToolupdateConstruct(screenCoords); break; case WIDX_MAZE_ENTRANCE: case WIDX_MAZE_EXIT: RideConstructionToolupdateEntranceExit(screenCoords); break; } } void OnToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override { switch (widgetIndex) { case WIDX_MAZE_DIRECTION_GROUPBOX: RideConstructionTooldownConstruct(screenCoords); break; case WIDX_MAZE_ENTRANCE: case WIDX_MAZE_EXIT: WindowMazeConstructionEntranceTooldown(screenCoords); break; } } void OnPrepareDraw() override { auto currentRide = GetRide(_currentRideIndex); auto ft = Formatter::Common(); if (currentRide != nullptr) { ft.Increment(4); currentRide->FormatNameTo(ft); } else { ft.Increment(4); ft.Add(STR_NONE); } } void OnDraw(DrawPixelInfo& dpi) override { DrawWidgets(dpi); } private: void WindowMazeConstructionEntranceMouseup(WidgetIndex widgetIndex) { if (ToolSet(*this, widgetIndex, Tool::Crosshair)) return; gRideEntranceExitPlaceType = widgetIndex == WIDX_MAZE_ENTRANCE ? ENTRANCE_TYPE_RIDE_ENTRANCE : ENTRANCE_TYPE_RIDE_EXIT; gRideEntranceExitPlaceRideIndex = rideId; gRideEntranceExitPlaceStationIndex = StationIndex::FromUnderlying(0); InputSetFlag(INPUT_FLAG_6, true); RideConstructionInvalidateCurrentTrack(); if (_rideConstructionState != RideConstructionState::EntranceExit) { gRideEntranceExitPlacePreviousRideConstructionState = _rideConstructionState; } _rideConstructionState = RideConstructionState::EntranceExit; WindowMazeConstructionUpdatePressedWidgets(); } void WindowMazeConstructionBuildModeMousedown(RideConstructionState rideConstructionState) { if (_rideConstructionState == RideConstructionState::EntranceExit) { ToolCancel(); } _rideConstructionState = rideConstructionState; WindowMazeConstructionUpdatePressedWidgets(); } void WindowMazeConstructionEntranceTooldown(const ScreenCoordsXY& screenCoords) { RideConstructionInvalidateCurrentTrack(); MapInvalidateSelectionRect(); gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; CoordsXYZD entranceOrExitCoords = RideGetEntranceOrExitPositionFromScreenPosition(screenCoords); if (entranceOrExitCoords.IsNull()) return; if (gRideEntranceExitPlaceDirection == INVALID_DIRECTION) return; RideId rideIndex = gRideEntranceExitPlaceRideIndex; auto rideEntranceExitPlaceAction = RideEntranceExitPlaceAction( entranceOrExitCoords, DirectionReverse(entranceOrExitCoords.direction), rideIndex, gRideEntranceExitPlaceStationIndex, gRideEntranceExitPlaceType == ENTRANCE_TYPE_RIDE_EXIT); rideEntranceExitPlaceAction.SetCallback([=, this](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(rideIndex); if (currentRide != nullptr && RideAreAllPossibleEntrancesAndExitsBuilt(*currentRide).Successful) { ToolCancel(); if (!currentRide->GetRideTypeDescriptor().HasFlag(RtdFlag::hasTrack)) WindowCloseByClass(WindowClass::RideConstruction); } else { gRideEntranceExitPlaceType = gRideEntranceExitPlaceType ^ 1; WindowInvalidateByClass(WindowClass::RideConstruction); auto newToolWidgetIndex = (gRideEntranceExitPlaceType == ENTRANCE_TYPE_RIDE_ENTRANCE) ? WIDX_MAZE_ENTRANCE : WIDX_MAZE_EXIT; ToolCancel(); ToolSet(*this, newToolWidgetIndex, Tool::Crosshair); WindowMazeConstructionUpdatePressedWidgets(); } }); auto res = GameActions::Execute(&rideEntranceExitPlaceAction); } void WindowMazeConstructionConstruct(int32_t direction) { int32_t x, y, z, actionFlags = 0, mode; _currentTrackSelectionFlags = 0; _rideConstructionNextArrowPulse = 0; gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; RideConstructionInvalidateCurrentTrack(); x = _currentTrackBegin.x + (CoordsDirectionDelta[direction].x / 2); y = _currentTrackBegin.y + (CoordsDirectionDelta[direction].y / 2); z = _currentTrackBegin.z; switch (_rideConstructionState) { case RideConstructionState::MazeBuild: mode = GC_SET_MAZE_TRACK_BUILD; break; case RideConstructionState::MazeMove: mode = GC_SET_MAZE_TRACK_MOVE; actionFlags = GAME_COMMAND_FLAG_ALLOW_DURING_PAUSED; break; default: case RideConstructionState::MazeFill: mode = GC_SET_MAZE_TRACK_FILL; break; } const auto loc = CoordsXYZD{ x, y, z, static_cast(direction) }; auto action = MazeSetTrackAction(loc, false, _currentRideIndex, mode); action.SetFlags(actionFlags); const auto res = GameActions::Execute(&action); if (res.Error != GameActions::Status::Ok) { return; } _currentTrackBegin.x = x; _currentTrackBegin.y = y; if (_rideConstructionState != RideConstructionState::MazeMove) { OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::PlaceItem, { x, y, z }); } } }; WindowBase* MazeConstructionOpen() { return WindowFocusOrCreate( WindowClass::RideConstruction, ScreenCoordsXY(0, 29), WW, WH, WF_NO_AUTO_CLOSE); } void WindowMazeConstructionUpdatePressedWidgets() { WindowBase* w; w = WindowFindByClass(WindowClass::RideConstruction); if (w == nullptr) return; uint64_t pressedWidgets = w->pressed_widgets; // Unpress all the mode buttons pressedWidgets &= ~EnumToFlag(WIDX_MAZE_BUILD_MODE); pressedWidgets &= ~EnumToFlag(WIDX_MAZE_MOVE_MODE); pressedWidgets &= ~EnumToFlag(WIDX_MAZE_FILL_MODE); pressedWidgets &= ~EnumToFlag(WIDX_MAZE_ENTRANCE); pressedWidgets &= ~EnumToFlag(WIDX_MAZE_EXIT); switch (_rideConstructionState) { case RideConstructionState::EntranceExit: if (isToolActive(WindowClass::RideConstruction, WIDX_MAZE_ENTRANCE)) { pressedWidgets |= EnumToFlag(WIDX_MAZE_ENTRANCE); } else { pressedWidgets |= EnumToFlag(WIDX_MAZE_EXIT); } break; case RideConstructionState::MazeBuild: pressedWidgets |= EnumToFlag(WIDX_MAZE_BUILD_MODE); break; case RideConstructionState::MazeMove: pressedWidgets |= EnumToFlag(WIDX_MAZE_MOVE_MODE); break; case RideConstructionState::MazeFill: pressedWidgets |= EnumToFlag(WIDX_MAZE_FILL_MODE); break; default: break; } w->pressed_widgets = pressedWidgets; w->Invalidate(); } } // namespace OpenRCT2::Ui::Windows