diff --git a/src/openrct2-ui/windows/Footpath.cpp b/src/openrct2-ui/windows/Footpath.cpp index ce8268c16a..9f508c216b 100644 --- a/src/openrct2-ui/windows/Footpath.cpp +++ b/src/openrct2-ui/windows/Footpath.cpp @@ -7,6 +7,7 @@ * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ +#include #include #include #include @@ -32,18 +33,7 @@ #include #include -static constexpr const StringId WINDOW_TITLE = STR_FOOTPATHS; -static constexpr const int32_t WH = 421; -static constexpr const int32_t WW = 106; -static constexpr const uint16_t ARROW_PULSE_DURATION = 200; - -static uint8_t _footpathConstructDirection; -static uint8_t _footpathConstructValidDirections; -static uint8_t _footpathConstructionMode; - -static std::vector> _dropdownEntries; - -static PathConstructFlags FootpathCreateConstructFlags(ObjectEntryIndex& type); +using namespace OpenRCT2::Ui; // clang-format off enum @@ -53,6 +43,16 @@ enum PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL }; +#pragma region Measurements + +static constexpr const StringId WINDOW_TITLE = STR_FOOTPATHS; +static constexpr const int32_t WH_WINDOW = 421; +static constexpr const int32_t WW_WINDOW = 106; + +#pragma endregion + +#pragma region Widgets + enum WindowFootpathWidgetIdx { WIDX_BACKGROUND, @@ -83,7 +83,7 @@ enum WindowFootpathWidgetIdx }; static rct_widget window_footpath_widgets[] = { - WINDOW_SHIM(WINDOW_TITLE, WW, WH), + WINDOW_SHIM(WINDOW_TITLE, WW_WINDOW, WH_WINDOW), // Type group MakeWidget({ 3, 17}, {100, 95}, WindowWidgetType::Groupbox, WindowColour::Primary , STR_TYPE ), @@ -113,38 +113,7 @@ static rct_widget window_footpath_widgets[] = { WIDGETS_END, }; -static void WindowFootpathClose(rct_window * w); -static void WindowFootpathMouseup(rct_window * w, WidgetIndex widgetIndex); -static void WindowFootpathMousedown(rct_window * w, WidgetIndex widgetIndex, rct_widget * widget); -static void WindowFootpathDropdown(rct_window * w, WidgetIndex widgetIndex, int32_t dropdownIndex); -static void WindowFootpathUpdate(rct_window * w); -static void WindowFootpathToolupdate(rct_window * w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); -static void WindowFootpathTooldown(rct_window * w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); -static void WindowFootpathTooldrag(rct_window * w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); -static void WindowFootpathToolup(rct_window * w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords); -static void WindowFootpathInvalidate(rct_window * w); -static void WindowFootpathPaint(rct_window * w, rct_drawpixelinfo * dpi); - -static WindowEventList window_footpath_events([](auto& events) -{ - events.close = &WindowFootpathClose; - events.mouse_up = &WindowFootpathMouseup; - events.mouse_down = &WindowFootpathMousedown; - events.dropdown = &WindowFootpathDropdown; - events.update = &WindowFootpathUpdate; - events.tool_update = &WindowFootpathToolupdate; - events.tool_down = &WindowFootpathTooldown; - events.tool_drag = &WindowFootpathTooldrag; - events.tool_up = &WindowFootpathToolup; - events.invalidate = &WindowFootpathInvalidate; - events.paint = &WindowFootpathPaint; -}); -// clang-format on - -static money32 _window_footpath_cost; -static uint32_t _footpathConstructionNextArrowPulse = 0; -static uint8_t _lastUpdatedCameraRotation = UINT8_MAX; -static bool _footpathErrorOccured; +#pragma endregion /** rct2: 0x0098D8B4 */ static constexpr const uint8_t DefaultPathSlope[] = { @@ -172,19 +141,1258 @@ static constexpr const uint8_t ConstructionPreviewImages[][4] = { { 16, 17, 18, 19 }, // Upwards { 18, 19, 16, 17 }, // Downwards }; +// clang-format on -static void WindowFootpathMousedownDirection(int32_t direction); -static void WindowFootpathMousedownSlope(int32_t slope); -static void WindowFootpathShowFootpathTypesDialog(rct_window* w, rct_widget* widget, bool showQueues); -static void WindowFootpathShowRailingsTypesDialog(rct_window* w, rct_widget* widget); -static void WindowFootpathSetProvisionalPathAtPoint(const ScreenCoordsXY& screenCoords); -static void WindowFootpathSetSelectionStartBridgeAtPoint(const ScreenCoordsXY& screenCoords); -static void WindowFootpathPlacePathAtPoint(const ScreenCoordsXY& screenCoords); -static void WindowFootpathStartBridgeAtPoint(const ScreenCoordsXY& screenCoords); -static void WindowFootpathConstruct(); -static void WindowFootpathRemove(); -static void WindowFootpathSetEnabledAndPressedWidgets(); -static void FootpathGetNextPathInfo(ObjectEntryIndex* type, CoordsXYZ& footpathLoc, int32_t* slope); +class FootpathWindow final : public Window +{ +private: + const uint16_t ARROW_PULSE_DURATION = 200; + + uint8_t _footpathConstructDirection; + uint8_t _footpathConstructValidDirections; + uint8_t _footpathConstructionMode; + + std::vector> _dropdownEntries; + + money32 _windowFootpathCost; + uint32_t _footpathConstructionNextArrowPulse = 0; + uint8_t _lastUpdatedCameraRotation = UINT8_MAX; + bool _footpathErrorOccured = false; + +public: +#pragma region Window Override Events + + void OnOpen() override + { + widgets = window_footpath_widgets; + + WindowInitScrollWidgets(*this); + window_push_others_right(*this); + show_gridlines(); + + tool_cancel(); + _footpathConstructionMode = PATH_CONSTRUCTION_MODE_LAND; + tool_set(*this, WIDX_CONSTRUCT_ON_LAND, Tool::PathDown); + input_set_flag(INPUT_FLAG_6, true); + _footpathErrorOccured = false; + WindowFootpathSetEnabledAndPressedWidgets(); + } + + void OnClose() override + { + FootpathProvisionalUpdate(); + viewport_set_visibility(0); + map_invalidate_map_selection_tiles(); + gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; + window_invalidate_by_class(WindowClass::TopToolbar); + hide_gridlines(); + } + + void OnUpdate() override + { + widget_invalidate(*this, WIDX_CONSTRUCT); + WindowFootpathUpdateProvisionalPathForBridgeMode(); + + // #2502: The camera might have changed rotation, so we need to update which directional buttons are pressed + uint8_t currentRotation = get_current_rotation(); + if (_lastUpdatedCameraRotation != currentRotation) + { + _lastUpdatedCameraRotation = currentRotation; + WindowFootpathSetEnabledAndPressedWidgets(); + } + + // Check tool + if (_footpathConstructionMode == PATH_CONSTRUCTION_MODE_LAND) + { + if (!(input_test_flag(INPUT_FLAG_TOOL_ACTIVE))) + { + Close(); + } + else if (gCurrentToolWidget.window_classification != WindowClass::Footpath) + { + Close(); + } + else if (gCurrentToolWidget.widget_index != WIDX_CONSTRUCT_ON_LAND) + { + Close(); + } + } + else if (_footpathConstructionMode == PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL_TOOL) + { + if (!(input_test_flag(INPUT_FLAG_TOOL_ACTIVE))) + { + Close(); + } + else if (gCurrentToolWidget.window_classification != WindowClass::Footpath) + { + Close(); + } + else if (gCurrentToolWidget.widget_index != WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL) + { + Close(); + } + } + } + + void OnMouseDown(WidgetIndex widgetIndex) override + { + switch (widgetIndex) + { + case WIDX_FOOTPATH_TYPE: + WindowFootpathShowFootpathTypesDialog(&window_footpath_widgets[widgetIndex], false); + break; + case WIDX_QUEUELINE_TYPE: + WindowFootpathShowFootpathTypesDialog(&window_footpath_widgets[widgetIndex], true); + break; + case WIDX_RAILINGS_TYPE: + WindowFootpathShowRailingsTypesDialog(&window_footpath_widgets[widgetIndex]); + break; + case WIDX_DIRECTION_NW: + WindowFootpathMousedownDirection(0); + break; + case WIDX_DIRECTION_NE: + WindowFootpathMousedownDirection(1); + break; + case WIDX_DIRECTION_SW: + WindowFootpathMousedownDirection(2); + break; + case WIDX_DIRECTION_SE: + WindowFootpathMousedownDirection(3); + break; + case WIDX_SLOPEDOWN: + WindowFootpathMousedownSlope(6); + break; + case WIDX_LEVEL: + WindowFootpathMousedownSlope(0); + break; + case WIDX_SLOPEUP: + WindowFootpathMousedownSlope(2); + break; + } + } + + void OnMouseUp(WidgetIndex widgetIndex) override + { + switch (widgetIndex) + { + case WIDX_CLOSE: + Close(); + break; + case WIDX_CONSTRUCT: + WindowFootpathConstruct(); + break; + case WIDX_REMOVE: + WindowFootpathRemove(); + break; + case WIDX_CONSTRUCT_ON_LAND: + if (_footpathConstructionMode == PATH_CONSTRUCTION_MODE_LAND) + { + break; + } + + _windowFootpathCost = MONEY32_UNDEFINED; + tool_cancel(); + FootpathProvisionalUpdate(); + map_invalidate_map_selection_tiles(); + gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; + _footpathConstructionMode = PATH_CONSTRUCTION_MODE_LAND; + tool_set(*this, WIDX_CONSTRUCT_ON_LAND, Tool::PathDown); + input_set_flag(INPUT_FLAG_6, true); + _footpathErrorOccured = false; + WindowFootpathSetEnabledAndPressedWidgets(); + break; + case WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL: + if (_footpathConstructionMode == PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL_TOOL) + { + break; + } + + _windowFootpathCost = MONEY32_UNDEFINED; + tool_cancel(); + FootpathProvisionalUpdate(); + map_invalidate_map_selection_tiles(); + gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; + _footpathConstructionMode = PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL_TOOL; + tool_set(*this, WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL, Tool::Crosshair); + input_set_flag(INPUT_FLAG_6, true); + _footpathErrorOccured = false; + WindowFootpathSetEnabledAndPressedWidgets(); + break; + } + } + + void OnDropdown(WidgetIndex widgetIndex, int32_t selectedIndex) override + { + if (selectedIndex < 0 || static_cast(selectedIndex) >= _dropdownEntries.size()) + return; + + auto entryIndex = _dropdownEntries[selectedIndex]; + if (widgetIndex == WIDX_FOOTPATH_TYPE) + { + gFootpathSelection.IsQueueSelected = false; + if (entryIndex.first == ObjectType::Paths) + { + gFootpathSelection.LegacyPath = entryIndex.second; + } + else + { + gFootpathSelection.LegacyPath = OBJECT_ENTRY_INDEX_NULL; + gFootpathSelection.NormalSurface = entryIndex.second; + } + } + else if (widgetIndex == WIDX_QUEUELINE_TYPE) + { + gFootpathSelection.IsQueueSelected = true; + if (entryIndex.first == ObjectType::Paths) + { + gFootpathSelection.LegacyPath = entryIndex.second; + } + else + { + gFootpathSelection.LegacyPath = OBJECT_ENTRY_INDEX_NULL; + gFootpathSelection.QueueSurface = entryIndex.second; + } + } + else if (widgetIndex == WIDX_RAILINGS_TYPE) + { + gFootpathSelection.Railings = entryIndex.second; + } + else + { + return; + } + + FootpathProvisionalUpdate(); + _windowFootpathCost = MONEY32_UNDEFINED; + Invalidate(); + } + + void OnToolUpdate(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override + { + if (widgetIndex == WIDX_CONSTRUCT_ON_LAND) + { + WindowFootpathSetProvisionalPathAtPoint(screenCoords); + } + else if (widgetIndex == WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL) + { + WindowFootpathSetSelectionStartBridgeAtPoint(screenCoords); + } + } + + void OnToolUp(WidgetIndex widgetIndex, const ScreenCoordsXY&) override + { + if (widgetIndex == WIDX_CONSTRUCT_ON_LAND) + { + _footpathErrorOccured = false; + } + } + + void OnToolDown(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override + { + if (widgetIndex == WIDX_CONSTRUCT_ON_LAND) + { + WindowFootpathPlacePathAtPoint(screenCoords); + } + else if (widgetIndex == WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL) + { + WindowFootpathStartBridgeAtPoint(screenCoords); + } + } + + void OnToolDrag(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) override + { + if (widgetIndex == WIDX_CONSTRUCT_ON_LAND) + { + WindowFootpathPlacePathAtPoint(screenCoords); + } + } + + void OnPrepareDraw() override + { + // Press / unpress footpath and queue type buttons + pressed_widgets &= ~(1ULL << WIDX_FOOTPATH_TYPE); + pressed_widgets &= ~(1ULL << WIDX_QUEUELINE_TYPE); + pressed_widgets |= gFootpathSelection.IsQueueSelected ? (1ULL << WIDX_QUEUELINE_TYPE) : (1ULL << WIDX_FOOTPATH_TYPE); + + // Enable / disable construct button + window_footpath_widgets[WIDX_CONSTRUCT].type = _footpathConstructionMode == PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL + ? WindowWidgetType::ImgBtn + : WindowWidgetType::Empty; + + if (gFootpathSelection.LegacyPath == OBJECT_ENTRY_INDEX_NULL) + { + window_footpath_widgets[WIDX_RAILINGS_TYPE].type = WindowWidgetType::FlatBtn; + } + else + { + window_footpath_widgets[WIDX_RAILINGS_TYPE].type = WindowWidgetType::Empty; + } + } + + void OnDraw(rct_drawpixelinfo& dpi) override + { + ScreenCoordsXY screenCoords; + WindowDrawWidgets(*this, &dpi); + WindowFootpathDrawDropdownButtons(&dpi); + + if (!IsWidgetDisabled(WIDX_CONSTRUCT)) + { + // Get construction image + uint8_t direction = (_footpathConstructDirection + get_current_rotation()) % 4; + uint8_t slope = 0; + if (gFootpathConstructSlope == 2) + { + slope = TILE_ELEMENT_SLOPE_N_CORNER_UP; + } + else if (gFootpathConstructSlope == 6) + { + slope = TILE_ELEMENT_SLOPE_E_CORNER_UP; + } + + std::optional baseImage; + if (gFootpathSelection.LegacyPath == OBJECT_ENTRY_INDEX_NULL) + { + auto selectedPath = gFootpathSelection.GetSelectedSurface(); + const auto* pathType = GetPathSurfaceEntry(selectedPath); + if (pathType != nullptr) + { + baseImage = pathType->BaseImageId; + } + } + else + { + auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); + auto* pathObj = static_cast( + objManager.GetLoadedObject(ObjectType::Paths, gFootpathSelection.LegacyPath)); + if (pathObj != nullptr) + { + auto pathEntry = reinterpret_cast(pathObj->GetLegacyData()); + if (gFootpathSelection.IsQueueSelected) + baseImage = pathEntry->GetQueueImage(); + else + baseImage = pathEntry->image; + } + } + + if (baseImage) + { + auto image = *baseImage + ConstructionPreviewImages[slope][direction]; + + // Draw construction image + screenCoords = this->windowPos + + ScreenCoordsXY{ window_footpath_widgets[WIDX_CONSTRUCT].midX(), + window_footpath_widgets[WIDX_CONSTRUCT].bottom - 60 }; + gfx_draw_sprite(&dpi, ImageId(image), screenCoords); + } + + // Draw build this... label + screenCoords = this->windowPos + + ScreenCoordsXY{ window_footpath_widgets[WIDX_CONSTRUCT].midX(), + window_footpath_widgets[WIDX_CONSTRUCT].bottom - 23 }; + DrawTextBasic(&dpi, screenCoords, STR_BUILD_THIS, {}, { TextAlignment::CENTRE }); + } + + // Draw cost + screenCoords = this->windowPos + + ScreenCoordsXY{ window_footpath_widgets[WIDX_CONSTRUCT].midX(), + window_footpath_widgets[WIDX_CONSTRUCT].bottom - 12 }; + if (_windowFootpathCost != MONEY32_UNDEFINED) + { + if (!(gParkFlags & PARK_FLAGS_NO_MONEY)) + { + auto ft = Formatter(); + ft.Add(_windowFootpathCost); + DrawTextBasic(&dpi, screenCoords, STR_COST_LABEL, ft, { TextAlignment::CENTRE }); + } + } + } + +#pragma endregion + +private: + /** + * + * rct2: 0x006A7760 + */ + void WindowFootpathUpdateProvisionalPathForBridgeMode() + { + if (_footpathConstructionMode != PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL) + { + return; + } + + // Recheck area for construction. Set by ride_construction window + if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_2) + { + FootpathProvisionalRemove(); + gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_2; + } + + // Update provisional bridge mode path + if (!(gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1)) + { + ObjectEntryIndex type; + ObjectEntryIndex railings = gFootpathSelection.Railings; + + CoordsXYZ footpathLoc; + int32_t slope; + FootpathGetNextPathInfo(&type, footpathLoc, &slope); + auto pathConstructFlags = FootpathCreateConstructFlags(type); + + _windowFootpathCost = FootpathProvisionalSet(type, railings, footpathLoc, slope, pathConstructFlags); + widget_invalidate(*this, WIDX_CONSTRUCT); + } + + auto curTime = Platform::GetTicks(); + + // Update little directional arrow on provisional bridge mode path + if (_footpathConstructionNextArrowPulse < curTime) + { + _footpathConstructionNextArrowPulse = curTime + ARROW_PULSE_DURATION; + + gProvisionalFootpath.Flags ^= PROVISIONAL_PATH_FLAG_SHOW_ARROW; + CoordsXYZ footpathLoc; + int32_t slope; + FootpathGetNextPathInfo(nullptr, footpathLoc, &slope); + gMapSelectArrowPosition = footpathLoc; + gMapSelectArrowDirection = _footpathConstructDirection; + if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_SHOW_ARROW) + { + gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_ARROW; + } + else + { + gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; + } + map_invalidate_tile_full(footpathLoc); + } + } + + void WindowFootpathDrawDropdownButtons(rct_drawpixelinfo* dpi) + { + if (gFootpathSelection.LegacyPath == OBJECT_ENTRY_INDEX_NULL) + { + // Set footpath and queue type button images + auto pathImage = static_cast(SPR_NONE); + auto queueImage = static_cast(SPR_NONE); + auto pathEntry = GetPathSurfaceEntry(gFootpathSelection.NormalSurface); + if (pathEntry != nullptr) + { + pathImage = pathEntry->PreviewImageId; + } + + pathEntry = GetPathSurfaceEntry(gFootpathSelection.QueueSurface); + if (pathEntry != nullptr) + { + queueImage = pathEntry->PreviewImageId; + } + + WindowFootpathDrawDropdownButton(dpi, WIDX_FOOTPATH_TYPE, pathImage); + WindowFootpathDrawDropdownButton(dpi, WIDX_QUEUELINE_TYPE, queueImage); + + // Set railing + auto railingsImage = static_cast(SPR_NONE); + auto railingsEntry = GetPathRailingsEntry(gFootpathSelection.Railings); + if (railingsEntry != nullptr) + { + railingsImage = railingsEntry->PreviewImageId; + } + WindowFootpathDrawDropdownButton(dpi, WIDX_RAILINGS_TYPE, railingsImage); + } + else + { + auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); + + // Set footpath and queue type button images + auto pathImage = static_cast(SPR_NONE); + auto queueImage = static_cast(SPR_NONE); + auto pathObj = static_cast( + objManager.GetLoadedObject(ObjectType::Paths, gFootpathSelection.LegacyPath)); + if (pathObj != nullptr) + { + auto pathEntry = reinterpret_cast(pathObj->GetLegacyData()); + pathImage = pathEntry->GetPreviewImage(); + queueImage = pathEntry->GetQueuePreviewImage(); + } + + WindowFootpathDrawDropdownButton(dpi, WIDX_FOOTPATH_TYPE, pathImage); + WindowFootpathDrawDropdownButton(dpi, WIDX_QUEUELINE_TYPE, queueImage); + } + } + + void WindowFootpathDrawDropdownButton(rct_drawpixelinfo* dpi, WidgetIndex widgetIndex, ImageIndex image) + { + const auto& widget = widgets[widgetIndex]; + gfx_draw_sprite(dpi, ImageId(image), { windowPos.x + widget.left, windowPos.y + widget.top }); + } + + /** + * + * rct2: 0x006A7F88 + */ + void WindowFootpathShowFootpathTypesDialog(rct_widget* widget, bool showQueues) + { + auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); + + uint32_t numPathTypes = 0; + // If the game is in sandbox mode, also show paths that are normally restricted to the scenario editor + bool showEditorPaths = ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode); + + _dropdownEntries.clear(); + std::optional defaultIndex; + for (ObjectEntryIndex i = 0; i < MAX_FOOTPATH_SURFACE_OBJECTS; i++) + { + const auto* pathType = static_cast( + objManager.GetLoadedObject(ObjectType::FootpathSurface, i)); + if (pathType == nullptr) + { + continue; + } + if ((pathType->Flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR) && !showEditorPaths) + { + continue; + } + if (showQueues != ((pathType->Flags & FOOTPATH_ENTRY_FLAG_IS_QUEUE) != 0)) + { + continue; + } + if (gFootpathSelection.LegacyPath == OBJECT_ENTRY_INDEX_NULL + && i == (showQueues ? gFootpathSelection.QueueSurface : gFootpathSelection.NormalSurface)) + { + defaultIndex = numPathTypes; + } + + gDropdownItems[numPathTypes].Format = STR_NONE; + Dropdown::SetImage(numPathTypes, ImageId(pathType->PreviewImageId)); + _dropdownEntries.push_back({ ObjectType::FootpathSurface, i }); + numPathTypes++; + } + + for (ObjectEntryIndex i = 0; i < MAX_PATH_OBJECTS; i++) + { + auto* pathObj = static_cast(objManager.GetLoadedObject(ObjectType::Paths, i)); + if (pathObj == nullptr) + { + continue; + } + + auto pathEntry = reinterpret_cast(pathObj->GetLegacyData()); + if ((pathEntry->flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR) && !showEditorPaths) + { + continue; + } + + if (gFootpathSelection.LegacyPath != OBJECT_ENTRY_INDEX_NULL && gFootpathSelection.LegacyPath == i) + { + defaultIndex = numPathTypes; + } + + gDropdownItems[numPathTypes].Format = STR_NONE; + Dropdown::SetImage( + numPathTypes, ImageId(showQueues ? pathEntry->GetQueuePreviewImage() : pathEntry->GetPreviewImage())); + _dropdownEntries.push_back({ ObjectType::Paths, i }); + numPathTypes++; + } + + auto itemsPerRow = DropdownGetAppropriateImageDropdownItemsPerRow(numPathTypes); + WindowDropdownShowImage( + windowPos.x + widget->left, windowPos.y + widget->top, widget->height() + 1, colours[1], 0, numPathTypes, 47, 36, + itemsPerRow); + if (defaultIndex) + gDropdownDefaultIndex = static_cast(*defaultIndex); + } + + void WindowFootpathShowRailingsTypesDialog(rct_widget* widget) + { + uint32_t numRailingsTypes = 0; + // If the game is in sandbox mode, also show paths that are normally restricted to the scenario editor + + _dropdownEntries.clear(); + std::optional defaultIndex; + for (int32_t i = 0; i < MAX_FOOTPATH_RAILINGS_OBJECTS; i++) + { + const auto* railingsEntry = GetPathRailingsEntry(i); + if (railingsEntry == nullptr) + { + continue; + } + if (i == gFootpathSelection.Railings) + { + defaultIndex = numRailingsTypes; + } + + gDropdownItems[numRailingsTypes].Format = STR_NONE; + Dropdown::SetImage(numRailingsTypes, ImageId(railingsEntry->PreviewImageId)); + _dropdownEntries.push_back({ ObjectType::FootpathRailings, i }); + numRailingsTypes++; + } + + auto itemsPerRow = DropdownGetAppropriateImageDropdownItemsPerRow(numRailingsTypes); + WindowDropdownShowImage( + windowPos.x + widget->left, windowPos.y + widget->top, widget->height() + 1, colours[1], 0, numRailingsTypes, 47, + 36, itemsPerRow); + if (defaultIndex) + gDropdownDefaultIndex = static_cast(*defaultIndex); + } + + /** + * + * rct2: 0x006A8111 0x006A8135 0x006A815C 0x006A8183 + */ + void WindowFootpathMousedownDirection(int32_t direction) + { + FootpathProvisionalUpdate(); + _footpathConstructDirection = (direction - get_current_rotation()) & 3; + _windowFootpathCost = MONEY32_UNDEFINED; + WindowFootpathSetEnabledAndPressedWidgets(); + } + + /** + * + * rct2: 0x006A81AA 0x006A81C5 0x006A81E0 + */ + void WindowFootpathMousedownSlope(int32_t slope) + { + FootpathProvisionalUpdate(); + gFootpathConstructSlope = slope; + _windowFootpathCost = MONEY32_UNDEFINED; + WindowFootpathSetEnabledAndPressedWidgets(); + } + + /** + * + * rct2: 0x006A81FB + */ + void WindowFootpathSetProvisionalPathAtPoint(const ScreenCoordsXY& screenCoords) + { + map_invalidate_selection_rect(); + gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; + + auto info = get_map_coordinates_from_pos( + screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain, ViewportInteractionItem::Footpath)); + + if (info.SpriteType == ViewportInteractionItem::None || info.Element == nullptr) + { + gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; + FootpathProvisionalUpdate(); + } + else + { + // Check for change + if ((gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1) + && gProvisionalFootpath.Position == CoordsXYZ{ info.Loc, info.Element->GetBaseZ() }) + { + return; + } + + // Set map selection + gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE; + gMapSelectType = MAP_SELECT_TYPE_FULL; + gMapSelectPositionA = info.Loc; + gMapSelectPositionB = info.Loc; + + FootpathProvisionalUpdate(); + + // Set provisional path + int32_t slope = 0; + switch (info.SpriteType) + { + case ViewportInteractionItem::Terrain: + { + auto surfaceElement = info.Element->AsSurface(); + if (surfaceElement != nullptr) + { + slope = DefaultPathSlope[surfaceElement->GetSlope() & TILE_ELEMENT_SURFACE_RAISED_CORNERS_MASK]; + } + break; + } + case ViewportInteractionItem::Footpath: + { + auto pathElement = info.Element->AsPath(); + if (pathElement != nullptr) + { + slope = pathElement->GetSlopeDirection(); + if (pathElement->IsSloped()) + { + slope |= FOOTPATH_PROPERTIES_FLAG_IS_SLOPED; + } + } + break; + } + default: + break; + } + auto z = info.Element->GetBaseZ(); + if (slope & RAISE_FOOTPATH_FLAG) + { + slope &= ~RAISE_FOOTPATH_FLAG; + z += PATH_HEIGHT_STEP; + } + + auto pathType = gFootpathSelection.GetSelectedSurface(); + auto constructFlags = FootpathCreateConstructFlags(pathType); + _windowFootpathCost = FootpathProvisionalSet( + pathType, gFootpathSelection.Railings, { info.Loc, z }, slope, constructFlags); + window_invalidate_by_class(WindowClass::Footpath); + } + } + + /** + * + * rct2: 0x006A8388 + */ + void WindowFootpathSetSelectionStartBridgeAtPoint(const ScreenCoordsXY& screenCoords) + { + int32_t direction; + TileElement* tileElement; + + map_invalidate_selection_rect(); + gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; + gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; + + auto mapCoords = FootpathBridgeGetInfoFromPos(screenCoords, &direction, &tileElement); + if (mapCoords.IsNull()) + { + return; + } + + gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE; + gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_ARROW; + gMapSelectType = MAP_SELECT_TYPE_FULL; + gMapSelectPositionA = mapCoords; + gMapSelectPositionB = mapCoords; + + int32_t z = tileElement->GetBaseZ(); + + if (tileElement->GetType() == TileElementType::Surface) + { + uint8_t slope = tileElement->AsSurface()->GetSlope(); + if (slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) + { + z += PATH_HEIGHT_STEP; + } // Add 2 for a slope + if (slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) + z += PATH_HEIGHT_STEP; // Add another 2 for a steep slope + } + + gMapSelectArrowPosition = CoordsXYZ{ mapCoords, z }; + gMapSelectArrowDirection = direction; + + map_invalidate_selection_rect(); + } + + /** + * + * rct2: 0x006A82C5 + */ + void WindowFootpathPlacePathAtPoint(const ScreenCoordsXY& screenCoords) + { + if (_footpathErrorOccured) + { + return; + } + + FootpathProvisionalUpdate(); + + const auto info = get_map_coordinates_from_pos( + screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain, ViewportInteractionItem::Footpath)); + + if (info.SpriteType == ViewportInteractionItem::None) + { + return; + } + + // Set path + auto slope = 0; + switch (info.SpriteType) + { + case ViewportInteractionItem::Terrain: + slope = DefaultPathSlope[info.Element->AsSurface()->GetSlope() & TILE_ELEMENT_SURFACE_RAISED_CORNERS_MASK]; + break; + case ViewportInteractionItem::Footpath: + slope = info.Element->AsPath()->GetSlopeDirection(); + if (info.Element->AsPath()->IsSloped()) + { + slope |= FOOTPATH_PROPERTIES_FLAG_IS_SLOPED; + } + break; + default: + break; + } + auto z = info.Element->GetBaseZ(); + if (slope & RAISE_FOOTPATH_FLAG) + { + slope &= ~RAISE_FOOTPATH_FLAG; + z += PATH_HEIGHT_STEP; + } + + // Try and place path + auto selectedType = gFootpathSelection.GetSelectedSurface(); + PathConstructFlags constructFlags = FootpathCreateConstructFlags(selectedType); + + auto footpathPlaceAction = FootpathPlaceAction( + { info.Loc, z }, slope, selectedType, gFootpathSelection.Railings, INVALID_DIRECTION, constructFlags); + footpathPlaceAction.SetCallback([this](const GameAction* ga, const GameActions::Result* result) { + if (result->Error == GameActions::Status::Ok) + { + // Don't play sound if it is no cost to prevent multiple sounds. TODO: make this work in no money scenarios + if (result->Cost != 0) + { + OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::PlaceItem, result->Position); + } + } + else + { + _footpathErrorOccured = true; + } + }); + GameActions::Execute(&footpathPlaceAction); + } + + /** + * + * rct2: 0x006A840F + */ + void WindowFootpathStartBridgeAtPoint(const ScreenCoordsXY& screenCoords) + { + int32_t z, direction; + TileElement* tileElement; + + auto mapCoords = FootpathBridgeGetInfoFromPos(screenCoords, &direction, &tileElement); + if (mapCoords.IsNull()) + { + return; + } + + if (tileElement->GetType() == TileElementType::Surface) + { + // If we start the path on a slope, the arrow is slightly raised, so we + // expect the path to be slightly raised as well. + uint8_t slope = tileElement->AsSurface()->GetSlope(); + z = tileElement->GetBaseZ(); + if (slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) + { + // Steep diagonal slope + z += 2 * PATH_HEIGHT_STEP; + } + else if (slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) + { + // Normal slope + z += PATH_HEIGHT_STEP; + } + } + else + { + z = tileElement->GetBaseZ(); + if (tileElement->GetType() == TileElementType::Path) + { + if (tileElement->AsPath()->IsSloped()) + { + if (direction == (tileElement->AsPath()->GetSlopeDirection())) + { + z += PATH_HEIGHT_STEP; + } + } + } + } + + tool_cancel(); + gFootpathConstructFromPosition = { mapCoords, z }; + _footpathConstructDirection = direction; + gProvisionalFootpath.Flags = 0; + gFootpathConstructSlope = 0; + _footpathConstructionMode = PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL; + _footpathConstructValidDirections = INVALID_DIRECTION; + WindowFootpathSetEnabledAndPressedWidgets(); + } + + /** + * Construct a piece of footpath while in bridge building mode. + * rct2: 0x006A79B7 + */ + void WindowFootpathConstruct() + { + _windowFootpathCost = MONEY32_UNDEFINED; + FootpathProvisionalUpdate(); + + ObjectEntryIndex type; + int32_t slope; + CoordsXYZ footpathLoc; + FootpathGetNextPathInfo(&type, footpathLoc, &slope); + + PathConstructFlags constructFlags = FootpathCreateConstructFlags(type); + + auto footpathPlaceAction = FootpathPlaceAction( + footpathLoc, slope, type, gFootpathSelection.Railings, _footpathConstructDirection, constructFlags); + footpathPlaceAction.SetCallback([=](const GameAction* ga, const GameActions::Result* result) { + if (result->Error == GameActions::Status::Ok) + { + OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::PlaceItem, result->Position); + + if (gFootpathConstructSlope == 0) + { + _footpathConstructValidDirections = INVALID_DIRECTION; + } + else + { + _footpathConstructValidDirections = _footpathConstructDirection; + } + + if (gFootpathGroundFlags & ELEMENT_IS_UNDERGROUND) + { + viewport_set_visibility(1); + } + + gFootpathConstructFromPosition = footpathLoc; + // If we have just built an upwards slope, the next path to construct is + // a bit higher. Note that the z returned by footpath_get_next_path_info + // already is lowered if we are building a downwards slope. + if (gFootpathConstructSlope == 2) + { + gFootpathConstructFromPosition.z += PATH_HEIGHT_STEP; + } + } + WindowFootpathSetEnabledAndPressedWidgets(); + }); + GameActions::Execute(&footpathPlaceAction); + } + + /** + * + * rct2: 0x006A78EF + */ + void FootpathRemoveTileElement(TileElement* tileElement) + { + auto z = tileElement->GetBaseZ(); + if (tileElement->AsPath()->IsSloped()) + { + uint8_t slopeDirection = tileElement->AsPath()->GetSlopeDirection(); + slopeDirection = DirectionReverse(slopeDirection); + if (slopeDirection == _footpathConstructDirection) + { + z += PATH_HEIGHT_STEP; + } + } + + // Find a connected edge + int32_t edge = DirectionReverse(_footpathConstructDirection); + if (!(tileElement->AsPath()->GetEdges() & (1 << edge))) + { + edge = (edge + 1) & 3; + if (!(tileElement->AsPath()->GetEdges() & (1 << edge))) + { + edge = (edge + 2) & 3; + if (!(tileElement->AsPath()->GetEdges() & (1 << edge))) + { + edge = (edge - 1) & 3; + if (!(tileElement->AsPath()->GetEdges() & (1 << edge))) + { + edge = DirectionReverse(edge); + } + } + } + } + + gFootpathConstructFromPosition.z = tileElement->GetBaseZ(); + auto action = FootpathRemoveAction(gFootpathConstructFromPosition); + GameActions::Execute(&action); + + // Move selection + edge = DirectionReverse(edge); + gFootpathConstructFromPosition.x -= CoordsDirectionDelta[edge].x; + gFootpathConstructFromPosition.y -= CoordsDirectionDelta[edge].y; + gFootpathConstructFromPosition.z = z; + _footpathConstructDirection = edge; + _footpathConstructValidDirections = INVALID_DIRECTION; + } + + /** + * + * rct2: 0x006A7873 + */ + TileElement* FootpathGetTileElementToRemove() + { + TileElement* tileElement; + int32_t z, zLow; + + if (!map_is_location_valid(gFootpathConstructFromPosition)) + { + return nullptr; + } + + z = std::min(255 * COORDS_Z_STEP, gFootpathConstructFromPosition.z); + zLow = z - PATH_HEIGHT_STEP; + + tileElement = map_get_first_element_at(gFootpathConstructFromPosition); + do + { + if (tileElement == nullptr) + break; + if (tileElement->GetType() == TileElementType::Path) + { + if (tileElement->GetBaseZ() == z) + { + if (tileElement->AsPath()->IsSloped()) + { + if (DirectionReverse(tileElement->AsPath()->GetSlopeDirection()) != _footpathConstructDirection) + { + continue; + } + } + + return tileElement; + } + if (tileElement->GetBaseZ() == zLow) + { + if (!tileElement->AsPath()->IsSloped()) + { + if ((tileElement->AsPath()->GetSlopeDirection()) == _footpathConstructDirection) + { + continue; + } + } + + return tileElement; + } + } + } while (!(tileElement++)->IsLastForTile()); + + return nullptr; + } + + /** + * + * rct2: 0x006A7863 + */ + void WindowFootpathRemove() + { + TileElement* tileElement; + + _windowFootpathCost = MONEY32_UNDEFINED; + FootpathProvisionalUpdate(); + + tileElement = FootpathGetTileElementToRemove(); + if (tileElement != nullptr) + { + FootpathRemoveTileElement(tileElement); + } + + WindowFootpathSetEnabledAndPressedWidgets(); + } + + /** + * + * rct2: 0x006A855C + */ + void WindowFootpathSetEnabledAndPressedWidgets() + { + if (_footpathConstructionMode == PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL) + { + map_invalidate_map_selection_tiles(); + gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_CONSTRUCT; + gMapSelectFlags |= MAP_SELECT_FLAG_GREEN; + + int32_t direction = _footpathConstructDirection; + gMapSelectionTiles.clear(); + gMapSelectionTiles.push_back({ gFootpathConstructFromPosition.x + CoordsDirectionDelta[direction].x, + gFootpathConstructFromPosition.y + CoordsDirectionDelta[direction].y }); + map_invalidate_map_selection_tiles(); + } + + uint64_t pressedWidgets = pressed_widgets + & ~((1LL << WIDX_DIRECTION_NW) | (1LL << WIDX_DIRECTION_NE) | (1LL << WIDX_DIRECTION_SW) + | (1LL << WIDX_DIRECTION_SE) | (1LL << WIDX_SLOPEDOWN) | (1LL << WIDX_LEVEL) | (1LL << WIDX_SLOPEUP)); + uint64_t disabledWidgets = 0; + int32_t currentRotation = get_current_rotation(); + if (_footpathConstructionMode >= PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL) + { + // Set pressed directional widget + int32_t direction = (_footpathConstructDirection + currentRotation) & 3; + pressedWidgets |= (1LL << (WIDX_DIRECTION_NW + direction)); + + // Set pressed slope widget + int32_t slope = gFootpathConstructSlope; + if (slope == TILE_ELEMENT_SLOPE_SE_SIDE_UP) + { + pressedWidgets |= (1ULL << WIDX_SLOPEDOWN); + } + else if (slope == TILE_ELEMENT_SLOPE_FLAT) + { + pressedWidgets |= (1ULL << WIDX_LEVEL); + } + else + { + pressedWidgets |= (1ULL << WIDX_SLOPEUP); + } + + // Enable / disable directional widgets + direction = _footpathConstructValidDirections; + if (direction != INVALID_DIRECTION) + { + disabledWidgets |= (1ULL << WIDX_DIRECTION_NW) | (1ULL << WIDX_DIRECTION_NE) | (1ULL << WIDX_DIRECTION_SW) + | (1ULL << WIDX_DIRECTION_SE); + + direction = (direction + currentRotation) & 3; + disabledWidgets &= ~(1 << (WIDX_DIRECTION_NW + direction)); + } + } + else + { + // Disable all bridge mode widgets + disabledWidgets |= (1ULL << WIDX_DIRECTION_GROUP) | (1ULL << WIDX_DIRECTION_NW) | (1ULL << WIDX_DIRECTION_NE) + | (1ULL << WIDX_DIRECTION_SW) | (1ULL << WIDX_DIRECTION_SE) | (1ULL << WIDX_SLOPE_GROUP) + | (1ULL << WIDX_SLOPEDOWN) | (1ULL << WIDX_LEVEL) | (1ULL << WIDX_SLOPEUP) | (1ULL << WIDX_CONSTRUCT) + | (1ULL << WIDX_REMOVE); + } + + pressed_widgets = pressedWidgets; + disabled_widgets = disabledWidgets; + Invalidate(); + } + + /** + * + * rct2: 0x006A7B20 + */ + void FootpathGetNextPathInfo(ObjectEntryIndex* type, CoordsXYZ& footpathLoc, int32_t* slope) + { + auto direction = _footpathConstructDirection; + footpathLoc.x = gFootpathConstructFromPosition.x + CoordsDirectionDelta[direction].x; + footpathLoc.y = gFootpathConstructFromPosition.y + CoordsDirectionDelta[direction].y; + footpathLoc.z = gFootpathConstructFromPosition.z; + if (type != nullptr) + { + *type = gFootpathSelection.GetSelectedSurface(); + } + *slope = TILE_ELEMENT_SLOPE_FLAT; + if (gFootpathConstructSlope != 0) + { + *slope = _footpathConstructDirection | TILE_ELEMENT_SLOPE_S_CORNER_UP; + if (gFootpathConstructSlope != 2) + { + footpathLoc.z -= PATH_HEIGHT_STEP; + *slope ^= TILE_ELEMENT_SLOPE_E_CORNER_UP; + } + } + } + + PathConstructFlags FootpathCreateConstructFlags(ObjectEntryIndex& type) + { + PathConstructFlags pathConstructFlags = 0; + if (gFootpathSelection.IsQueueSelected) + pathConstructFlags |= PathConstructFlag::IsQueue; + if (gFootpathSelection.LegacyPath != OBJECT_ENTRY_INDEX_NULL) + { + pathConstructFlags |= PathConstructFlag::IsLegacyPathObject; + type = gFootpathSelection.LegacyPath; + } + return pathConstructFlags; + } + +#pragma region Keyboard Shortcuts Events +public: + void KeyboardShortcutTurnLeft() + { + if (IsWidgetDisabled(WIDX_DIRECTION_NW) || IsWidgetDisabled(WIDX_DIRECTION_NE) || IsWidgetDisabled(WIDX_DIRECTION_SW) + || IsWidgetDisabled(WIDX_DIRECTION_SE) || _footpathConstructionMode != 2) + { + return; + } + int32_t currentRotation = get_current_rotation(); + int32_t turnedRotation = _footpathConstructDirection - currentRotation + (currentRotation % 2 == 1 ? 1 : -1); + WindowFootpathMousedownDirection(turnedRotation); + } + + void KeyboardShortcutTurnRight() + { + if (IsWidgetDisabled(WIDX_DIRECTION_NW) || IsWidgetDisabled(WIDX_DIRECTION_NE) || IsWidgetDisabled(WIDX_DIRECTION_SW) + || IsWidgetDisabled(WIDX_DIRECTION_SE) || _footpathConstructionMode != 2) + { + return; + } + int32_t currentRotation = get_current_rotation(); + int32_t turnedRotation = _footpathConstructDirection - currentRotation + (currentRotation % 2 == 1 ? -1 : 1); + WindowFootpathMousedownDirection(turnedRotation); + } + + void KeyboardShortcutShortcutSlopeDown() + { + if (IsWidgetDisabled(WIDX_SLOPEDOWN) || IsWidgetDisabled(WIDX_LEVEL) || IsWidgetDisabled(WIDX_SLOPEUP) + || widgets[WIDX_LEVEL].type == WindowWidgetType::Empty) + { + return; + } + + switch (gFootpathConstructSlope) + { + case 0: + OnMouseDown(WIDX_SLOPEDOWN); + break; + case 2: + OnMouseDown(WIDX_LEVEL); + break; + default: + case 6: + return; + } + } + + void KeyboardShortcutSlopeUp() + { + if (IsWidgetDisabled(WIDX_SLOPEDOWN) || IsWidgetDisabled(WIDX_LEVEL) || IsWidgetDisabled(WIDX_SLOPEUP) + || widgets[WIDX_LEVEL].type == WindowWidgetType::Empty) + { + return; + } + + switch (gFootpathConstructSlope) + { + case 6: + OnMouseDown(WIDX_LEVEL); + break; + case 0: + OnMouseDown(WIDX_SLOPEUP); + break; + default: + case 2: + return; + } + } + + void KeyboardShortcutSlopeLevel() + { + if (IsWidgetDisabled(WIDX_SLOPEDOWN) || IsWidgetDisabled(WIDX_LEVEL) || IsWidgetDisabled(WIDX_SLOPEUP) + || widgets[WIDX_LEVEL].type == WindowWidgetType::Empty || gFootpathConstructSlope == 0) + { + return; + } + + OnMouseDown(WIDX_LEVEL); + } + + void KeyboardShortcutDemolishCurrent() + { + if (IsWidgetDisabled(WIDX_REMOVE) || widgets[WIDX_REMOVE].type == WindowWidgetType::Empty + || (!gCheatsBuildInPauseMode && game_is_paused())) + { + return; + } + + WindowFootpathRemove(); + } + + void KeyboardShortcutBuildCurrent() + { + if (IsWidgetDisabled(WIDX_CONSTRUCT) || widgets[WIDX_CONSTRUCT].type == WindowWidgetType::Empty) + { + return; + } + + OnMouseUp(WIDX_CONSTRUCT); + } + +#pragma endregion +}; /** * @@ -198,1281 +1406,88 @@ rct_window* WindowFootpathOpen() return nullptr; } - // Check if window is already open - rct_window* window = window_bring_to_front_by_class(WindowClass::Footpath); - if (window != nullptr) - { - return window; - } - - window = WindowCreate(ScreenCoordsXY(0, 29), WW, WH, &window_footpath_events, WindowClass::Footpath, 0); - window->widgets = window_footpath_widgets; - - WindowInitScrollWidgets(*window); - window_push_others_right(*window); - show_gridlines(); - - tool_cancel(); - _footpathConstructionMode = PATH_CONSTRUCTION_MODE_LAND; - tool_set(*window, WIDX_CONSTRUCT_ON_LAND, Tool::PathDown); - input_set_flag(INPUT_FLAG_6, true); - _footpathErrorOccured = false; - WindowFootpathSetEnabledAndPressedWidgets(); - - return window; -} - -/** - * - * rct2: 0x006A852F - */ -static void WindowFootpathClose(rct_window* w) -{ - FootpathProvisionalUpdate(); - viewport_set_visibility(0); - map_invalidate_map_selection_tiles(); - gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; - window_invalidate_by_class(WindowClass::TopToolbar); - hide_gridlines(); -} - -/** - * - * rct2: 0x006A7E92 - */ -static void WindowFootpathMouseup(rct_window* w, WidgetIndex widgetIndex) -{ - switch (widgetIndex) - { - case WIDX_CLOSE: - window_close(*w); - break; - case WIDX_CONSTRUCT: - WindowFootpathConstruct(); - break; - case WIDX_REMOVE: - WindowFootpathRemove(); - break; - case WIDX_CONSTRUCT_ON_LAND: - if (_footpathConstructionMode == PATH_CONSTRUCTION_MODE_LAND) - { - break; - } - - _window_footpath_cost = MONEY32_UNDEFINED; - tool_cancel(); - FootpathProvisionalUpdate(); - map_invalidate_map_selection_tiles(); - gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; - _footpathConstructionMode = PATH_CONSTRUCTION_MODE_LAND; - tool_set(*w, WIDX_CONSTRUCT_ON_LAND, Tool::PathDown); - input_set_flag(INPUT_FLAG_6, true); - _footpathErrorOccured = false; - WindowFootpathSetEnabledAndPressedWidgets(); - break; - case WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL: - if (_footpathConstructionMode == PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL_TOOL) - { - break; - } - - _window_footpath_cost = MONEY32_UNDEFINED; - tool_cancel(); - FootpathProvisionalUpdate(); - map_invalidate_map_selection_tiles(); - gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_CONSTRUCT; - _footpathConstructionMode = PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL_TOOL; - tool_set(*w, WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL, Tool::Crosshair); - input_set_flag(INPUT_FLAG_6, true); - _footpathErrorOccured = false; - WindowFootpathSetEnabledAndPressedWidgets(); - break; - } -} - -/** - * - * rct2: 0x006A7EC5 - */ -static void WindowFootpathMousedown(rct_window* w, WidgetIndex widgetIndex, rct_widget* widget) -{ - switch (widgetIndex) - { - case WIDX_FOOTPATH_TYPE: - WindowFootpathShowFootpathTypesDialog(w, widget, false); - break; - case WIDX_QUEUELINE_TYPE: - WindowFootpathShowFootpathTypesDialog(w, widget, true); - break; - case WIDX_RAILINGS_TYPE: - WindowFootpathShowRailingsTypesDialog(w, widget); - break; - case WIDX_DIRECTION_NW: - WindowFootpathMousedownDirection(0); - break; - case WIDX_DIRECTION_NE: - WindowFootpathMousedownDirection(1); - break; - case WIDX_DIRECTION_SW: - WindowFootpathMousedownDirection(2); - break; - case WIDX_DIRECTION_SE: - WindowFootpathMousedownDirection(3); - break; - case WIDX_SLOPEDOWN: - WindowFootpathMousedownSlope(6); - break; - case WIDX_LEVEL: - WindowFootpathMousedownSlope(0); - break; - case WIDX_SLOPEUP: - WindowFootpathMousedownSlope(2); - break; - } -} - -/** - * - * rct2: 0x006A7F18 - */ -static void WindowFootpathDropdown(rct_window* w, WidgetIndex widgetIndex, int32_t dropdownIndex) -{ - if (dropdownIndex < 0 || static_cast(dropdownIndex) >= _dropdownEntries.size()) - return; - - auto entryIndex = _dropdownEntries[dropdownIndex]; - if (widgetIndex == WIDX_FOOTPATH_TYPE) - { - gFootpathSelection.IsQueueSelected = false; - if (entryIndex.first == ObjectType::Paths) - { - gFootpathSelection.LegacyPath = entryIndex.second; - } - else - { - gFootpathSelection.LegacyPath = OBJECT_ENTRY_INDEX_NULL; - gFootpathSelection.NormalSurface = entryIndex.second; - } - } - else if (widgetIndex == WIDX_QUEUELINE_TYPE) - { - gFootpathSelection.IsQueueSelected = true; - if (entryIndex.first == ObjectType::Paths) - { - gFootpathSelection.LegacyPath = entryIndex.second; - } - else - { - gFootpathSelection.LegacyPath = OBJECT_ENTRY_INDEX_NULL; - gFootpathSelection.QueueSurface = entryIndex.second; - } - } - else if (widgetIndex == WIDX_RAILINGS_TYPE) - { - gFootpathSelection.Railings = entryIndex.second; - } - else - { - return; - } - - FootpathProvisionalUpdate(); - _window_footpath_cost = MONEY32_UNDEFINED; - w->Invalidate(); -} - -/** - * - * rct2: 0x006A8032 - */ -static void WindowFootpathToolupdate(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) -{ - if (widgetIndex == WIDX_CONSTRUCT_ON_LAND) - { - WindowFootpathSetProvisionalPathAtPoint(screenCoords); - } - else if (widgetIndex == WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL) - { - WindowFootpathSetSelectionStartBridgeAtPoint(screenCoords); - } -} - -/** - * - * rct2: 0x006A8047 - */ -static void WindowFootpathTooldown(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) -{ - if (widgetIndex == WIDX_CONSTRUCT_ON_LAND) - { - WindowFootpathPlacePathAtPoint(screenCoords); - } - else if (widgetIndex == WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL) - { - WindowFootpathStartBridgeAtPoint(screenCoords); - } -} - -/** - * - * rct2: 0x006A8067 - */ -static void WindowFootpathTooldrag(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) -{ - if (widgetIndex == WIDX_CONSTRUCT_ON_LAND) - { - WindowFootpathPlacePathAtPoint(screenCoords); - } -} - -/** - * - * rct2: 0x006A8066 - */ -static void WindowFootpathToolup(rct_window* w, WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) -{ - if (widgetIndex == WIDX_CONSTRUCT_ON_LAND) - { - _footpathErrorOccured = false; - } -} - -/** - * - * rct2: 0x006A7760 - */ -static void WindowFootpathUpdateProvisionalPathForBridgeMode(rct_window* w) -{ - if (_footpathConstructionMode != PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL) - { - return; - } - - // Recheck area for construction. Set by ride_construction window - if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_2) - { - FootpathProvisionalRemove(); - gProvisionalFootpath.Flags &= ~PROVISIONAL_PATH_FLAG_2; - } - - // Update provisional bridge mode path - if (!(gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1)) - { - ObjectEntryIndex type; - ObjectEntryIndex railings = gFootpathSelection.Railings; - - CoordsXYZ footpathLoc; - int32_t slope; - FootpathGetNextPathInfo(&type, footpathLoc, &slope); - auto pathConstructFlags = FootpathCreateConstructFlags(type); - - _window_footpath_cost = FootpathProvisionalSet(type, railings, footpathLoc, slope, pathConstructFlags); - widget_invalidate(*w, WIDX_CONSTRUCT); - } - - auto curTime = Platform::GetTicks(); - - // Update little directional arrow on provisional bridge mode path - if (_footpathConstructionNextArrowPulse < curTime) - { - _footpathConstructionNextArrowPulse = curTime + ARROW_PULSE_DURATION; - - gProvisionalFootpath.Flags ^= PROVISIONAL_PATH_FLAG_SHOW_ARROW; - CoordsXYZ footpathLoc; - int32_t slope; - FootpathGetNextPathInfo(nullptr, footpathLoc, &slope); - gMapSelectArrowPosition = footpathLoc; - gMapSelectArrowDirection = _footpathConstructDirection; - if (gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_SHOW_ARROW) - { - gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_ARROW; - } - else - { - gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; - } - map_invalidate_tile_full(footpathLoc); - } -} - -/** - * - * rct2: 0x006A84BB - */ -static void WindowFootpathUpdate(rct_window* w) -{ - widget_invalidate(*w, WIDX_CONSTRUCT); - WindowFootpathUpdateProvisionalPathForBridgeMode(w); - - // #2502: The camera might have changed rotation, so we need to update which directional buttons are pressed - uint8_t currentRotation = get_current_rotation(); - if (_lastUpdatedCameraRotation != currentRotation) - { - _lastUpdatedCameraRotation = currentRotation; - WindowFootpathSetEnabledAndPressedWidgets(); - } - - // Check tool - if (_footpathConstructionMode == PATH_CONSTRUCTION_MODE_LAND) - { - if (!(input_test_flag(INPUT_FLAG_TOOL_ACTIVE))) - { - window_close(*w); - } - else if (gCurrentToolWidget.window_classification != WindowClass::Footpath) - { - window_close(*w); - } - else if (gCurrentToolWidget.widget_index != WIDX_CONSTRUCT_ON_LAND) - { - window_close(*w); - } - } - else if (_footpathConstructionMode == PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL_TOOL) - { - if (!(input_test_flag(INPUT_FLAG_TOOL_ACTIVE))) - { - window_close(*w); - } - else if (gCurrentToolWidget.window_classification != WindowClass::Footpath) - { - window_close(*w); - } - else if (gCurrentToolWidget.widget_index != WIDX_CONSTRUCT_BRIDGE_OR_TUNNEL) - { - window_close(*w); - } - } -} - -/** - * - * rct2: 0x006A7D1C - */ -static void WindowFootpathInvalidate(rct_window* w) -{ - // Press / unpress footpath and queue type buttons - w->pressed_widgets &= ~(1ULL << WIDX_FOOTPATH_TYPE); - w->pressed_widgets &= ~(1ULL << WIDX_QUEUELINE_TYPE); - w->pressed_widgets |= gFootpathSelection.IsQueueSelected ? (1ULL << WIDX_QUEUELINE_TYPE) : (1ULL << WIDX_FOOTPATH_TYPE); - - // Enable / disable construct button - window_footpath_widgets[WIDX_CONSTRUCT].type = _footpathConstructionMode == PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL - ? WindowWidgetType::ImgBtn - : WindowWidgetType::Empty; - - if (gFootpathSelection.LegacyPath == OBJECT_ENTRY_INDEX_NULL) - { - window_footpath_widgets[WIDX_RAILINGS_TYPE].type = WindowWidgetType::FlatBtn; - } - else - { - window_footpath_widgets[WIDX_RAILINGS_TYPE].type = WindowWidgetType::Empty; - } -} - -static void WindowFootpathDrawDropdownButton(rct_window* w, rct_drawpixelinfo* dpi, WidgetIndex widgetIndex, ImageIndex image) -{ - const auto& widget = w->widgets[widgetIndex]; - gfx_draw_sprite(dpi, ImageId(image), { w->windowPos.x + widget.left, w->windowPos.y + widget.top }); -} - -static void WindowFootpathDrawDropdownButtons(rct_window* w, rct_drawpixelinfo* dpi) -{ - if (gFootpathSelection.LegacyPath == OBJECT_ENTRY_INDEX_NULL) - { - // Set footpath and queue type button images - auto pathImage = static_cast(SPR_NONE); - auto queueImage = static_cast(SPR_NONE); - auto pathEntry = GetPathSurfaceEntry(gFootpathSelection.NormalSurface); - if (pathEntry != nullptr) - { - pathImage = pathEntry->PreviewImageId; - } - - pathEntry = GetPathSurfaceEntry(gFootpathSelection.QueueSurface); - if (pathEntry != nullptr) - { - queueImage = pathEntry->PreviewImageId; - } - - WindowFootpathDrawDropdownButton(w, dpi, WIDX_FOOTPATH_TYPE, pathImage); - WindowFootpathDrawDropdownButton(w, dpi, WIDX_QUEUELINE_TYPE, queueImage); - - // Set railing - auto railingsImage = static_cast(SPR_NONE); - auto railingsEntry = GetPathRailingsEntry(gFootpathSelection.Railings); - if (railingsEntry != nullptr) - { - railingsImage = railingsEntry->PreviewImageId; - } - WindowFootpathDrawDropdownButton(w, dpi, WIDX_RAILINGS_TYPE, railingsImage); - } - else - { - auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); - - // Set footpath and queue type button images - auto pathImage = static_cast(SPR_NONE); - auto queueImage = static_cast(SPR_NONE); - auto pathObj = static_cast( - objManager.GetLoadedObject(ObjectType::Paths, gFootpathSelection.LegacyPath)); - if (pathObj != nullptr) - { - auto pathEntry = reinterpret_cast(pathObj->GetLegacyData()); - pathImage = pathEntry->GetPreviewImage(); - queueImage = pathEntry->GetQueuePreviewImage(); - } - - WindowFootpathDrawDropdownButton(w, dpi, WIDX_FOOTPATH_TYPE, pathImage); - WindowFootpathDrawDropdownButton(w, dpi, WIDX_QUEUELINE_TYPE, queueImage); - } -} - -/** - * - * rct2: 0x006A7D8B - */ -static void WindowFootpathPaint(rct_window* w, rct_drawpixelinfo* dpi) -{ - ScreenCoordsXY screenCoords; - WindowDrawWidgets(*w, dpi); - WindowFootpathDrawDropdownButtons(w, dpi); - - if (!WidgetIsDisabled(*w, WIDX_CONSTRUCT)) - { - // Get construction image - uint8_t direction = (_footpathConstructDirection + get_current_rotation()) % 4; - uint8_t slope = 0; - if (gFootpathConstructSlope == 2) - { - slope = TILE_ELEMENT_SLOPE_N_CORNER_UP; - } - else if (gFootpathConstructSlope == 6) - { - slope = TILE_ELEMENT_SLOPE_E_CORNER_UP; - } - - std::optional baseImage; - if (gFootpathSelection.LegacyPath == OBJECT_ENTRY_INDEX_NULL) - { - auto selectedPath = gFootpathSelection.GetSelectedSurface(); - const auto* pathType = GetPathSurfaceEntry(selectedPath); - if (pathType != nullptr) - { - baseImage = pathType->BaseImageId; - } - } - else - { - auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); - auto* pathObj = static_cast( - objManager.GetLoadedObject(ObjectType::Paths, gFootpathSelection.LegacyPath)); - if (pathObj != nullptr) - { - auto pathEntry = reinterpret_cast(pathObj->GetLegacyData()); - if (gFootpathSelection.IsQueueSelected) - baseImage = pathEntry->GetQueueImage(); - else - baseImage = pathEntry->image; - } - } - - if (baseImage) - { - auto image = *baseImage + ConstructionPreviewImages[slope][direction]; - - // Draw construction image - screenCoords = w->windowPos - + ScreenCoordsXY{ window_footpath_widgets[WIDX_CONSTRUCT].midX(), - window_footpath_widgets[WIDX_CONSTRUCT].bottom - 60 }; - gfx_draw_sprite(dpi, ImageId(image), screenCoords); - } - - // Draw build this... label - screenCoords = w->windowPos - + ScreenCoordsXY{ window_footpath_widgets[WIDX_CONSTRUCT].midX(), - window_footpath_widgets[WIDX_CONSTRUCT].bottom - 23 }; - DrawTextBasic(dpi, screenCoords, STR_BUILD_THIS, {}, { TextAlignment::CENTRE }); - } - - // Draw cost - screenCoords = w->windowPos - + ScreenCoordsXY{ window_footpath_widgets[WIDX_CONSTRUCT].midX(), window_footpath_widgets[WIDX_CONSTRUCT].bottom - 12 }; - if (_window_footpath_cost != MONEY32_UNDEFINED) - { - if (!(gParkFlags & PARK_FLAGS_NO_MONEY)) - { - auto ft = Formatter(); - ft.Add(_window_footpath_cost); - DrawTextBasic(dpi, screenCoords, STR_COST_LABEL, ft, { TextAlignment::CENTRE }); - } - } -} - -/** - * - * rct2: 0x006A7F88 - */ -static void WindowFootpathShowFootpathTypesDialog(rct_window* w, rct_widget* widget, bool showQueues) -{ - auto& objManager = OpenRCT2::GetContext()->GetObjectManager(); - - uint32_t numPathTypes = 0; - // If the game is in sandbox mode, also show paths that are normally restricted to the scenario editor - bool showEditorPaths = ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || gCheatsSandboxMode); - - _dropdownEntries.clear(); - std::optional defaultIndex; - for (ObjectEntryIndex i = 0; i < MAX_FOOTPATH_SURFACE_OBJECTS; i++) - { - const auto* pathType = static_cast(objManager.GetLoadedObject(ObjectType::FootpathSurface, i)); - if (pathType == nullptr) - { - continue; - } - if ((pathType->Flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR) && !showEditorPaths) - { - continue; - } - if (showQueues != ((pathType->Flags & FOOTPATH_ENTRY_FLAG_IS_QUEUE) != 0)) - { - continue; - } - if (gFootpathSelection.LegacyPath == OBJECT_ENTRY_INDEX_NULL - && i == (showQueues ? gFootpathSelection.QueueSurface : gFootpathSelection.NormalSurface)) - { - defaultIndex = numPathTypes; - } - - gDropdownItems[numPathTypes].Format = STR_NONE; - Dropdown::SetImage(numPathTypes, ImageId(pathType->PreviewImageId)); - _dropdownEntries.push_back({ ObjectType::FootpathSurface, i }); - numPathTypes++; - } - - for (ObjectEntryIndex i = 0; i < MAX_PATH_OBJECTS; i++) - { - auto* pathObj = static_cast(objManager.GetLoadedObject(ObjectType::Paths, i)); - if (pathObj == nullptr) - { - continue; - } - - auto pathEntry = reinterpret_cast(pathObj->GetLegacyData()); - if ((pathEntry->flags & FOOTPATH_ENTRY_FLAG_SHOW_ONLY_IN_SCENARIO_EDITOR) && !showEditorPaths) - { - continue; - } - - if (gFootpathSelection.LegacyPath != OBJECT_ENTRY_INDEX_NULL && gFootpathSelection.LegacyPath == i) - { - defaultIndex = numPathTypes; - } - - gDropdownItems[numPathTypes].Format = STR_NONE; - Dropdown::SetImage( - numPathTypes, ImageId(showQueues ? pathEntry->GetQueuePreviewImage() : pathEntry->GetPreviewImage())); - _dropdownEntries.push_back({ ObjectType::Paths, i }); - numPathTypes++; - } - - auto itemsPerRow = DropdownGetAppropriateImageDropdownItemsPerRow(numPathTypes); - WindowDropdownShowImage( - w->windowPos.x + widget->left, w->windowPos.y + widget->top, widget->height() + 1, w->colours[1], 0, numPathTypes, 47, - 36, itemsPerRow); - if (defaultIndex) - gDropdownDefaultIndex = static_cast(*defaultIndex); -} - -static void WindowFootpathShowRailingsTypesDialog(rct_window* w, rct_widget* widget) -{ - uint32_t numRailingsTypes = 0; - // If the game is in sandbox mode, also show paths that are normally restricted to the scenario editor - - _dropdownEntries.clear(); - std::optional defaultIndex; - for (int32_t i = 0; i < MAX_FOOTPATH_RAILINGS_OBJECTS; i++) - { - const auto* railingsEntry = GetPathRailingsEntry(i); - if (railingsEntry == nullptr) - { - continue; - } - if (i == gFootpathSelection.Railings) - { - defaultIndex = numRailingsTypes; - } - - gDropdownItems[numRailingsTypes].Format = STR_NONE; - Dropdown::SetImage(numRailingsTypes, ImageId(railingsEntry->PreviewImageId)); - _dropdownEntries.push_back({ ObjectType::FootpathRailings, i }); - numRailingsTypes++; - } - - auto itemsPerRow = DropdownGetAppropriateImageDropdownItemsPerRow(numRailingsTypes); - WindowDropdownShowImage( - w->windowPos.x + widget->left, w->windowPos.y + widget->top, widget->height() + 1, w->colours[1], 0, numRailingsTypes, - 47, 36, itemsPerRow); - if (defaultIndex) - gDropdownDefaultIndex = static_cast(*defaultIndex); -} - -/** - * - * rct2: 0x006A8111 0x006A8135 0x006A815C 0x006A8183 - */ -static void WindowFootpathMousedownDirection(int32_t direction) -{ - FootpathProvisionalUpdate(); - _footpathConstructDirection = (direction - get_current_rotation()) & 3; - _window_footpath_cost = MONEY32_UNDEFINED; - WindowFootpathSetEnabledAndPressedWidgets(); -} - -/** - * - * rct2: 0x006A81AA 0x006A81C5 0x006A81E0 - */ -static void WindowFootpathMousedownSlope(int32_t slope) -{ - FootpathProvisionalUpdate(); - gFootpathConstructSlope = slope; - _window_footpath_cost = MONEY32_UNDEFINED; - WindowFootpathSetEnabledAndPressedWidgets(); -} - -/** - * - * rct2: 0x006A81FB - */ -static void WindowFootpathSetProvisionalPathAtPoint(const ScreenCoordsXY& screenCoords) -{ - map_invalidate_selection_rect(); - gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; - - auto info = get_map_coordinates_from_pos( - screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain, ViewportInteractionItem::Footpath)); - - if (info.SpriteType == ViewportInteractionItem::None || info.Element == nullptr) - { - gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; - FootpathProvisionalUpdate(); - } - else - { - // Check for change - if ((gProvisionalFootpath.Flags & PROVISIONAL_PATH_FLAG_1) - && gProvisionalFootpath.Position == CoordsXYZ{ info.Loc, info.Element->GetBaseZ() }) - { - return; - } - - // Set map selection - gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE; - gMapSelectType = MAP_SELECT_TYPE_FULL; - gMapSelectPositionA = info.Loc; - gMapSelectPositionB = info.Loc; - - FootpathProvisionalUpdate(); - - // Set provisional path - int32_t slope = 0; - switch (info.SpriteType) - { - case ViewportInteractionItem::Terrain: - { - auto surfaceElement = info.Element->AsSurface(); - if (surfaceElement != nullptr) - { - slope = DefaultPathSlope[surfaceElement->GetSlope() & TILE_ELEMENT_SURFACE_RAISED_CORNERS_MASK]; - } - break; - } - case ViewportInteractionItem::Footpath: - { - auto pathElement = info.Element->AsPath(); - if (pathElement != nullptr) - { - slope = pathElement->GetSlopeDirection(); - if (pathElement->IsSloped()) - { - slope |= FOOTPATH_PROPERTIES_FLAG_IS_SLOPED; - } - } - break; - } - default: - break; - } - auto z = info.Element->GetBaseZ(); - if (slope & RAISE_FOOTPATH_FLAG) - { - slope &= ~RAISE_FOOTPATH_FLAG; - z += PATH_HEIGHT_STEP; - } - - auto pathType = gFootpathSelection.GetSelectedSurface(); - auto constructFlags = FootpathCreateConstructFlags(pathType); - _window_footpath_cost = FootpathProvisionalSet( - pathType, gFootpathSelection.Railings, { info.Loc, z }, slope, constructFlags); - window_invalidate_by_class(WindowClass::Footpath); - } -} - -/** - * - * rct2: 0x006A8388 - */ -static void WindowFootpathSetSelectionStartBridgeAtPoint(const ScreenCoordsXY& screenCoords) -{ - int32_t direction; - TileElement* tileElement; - - map_invalidate_selection_rect(); - gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE; - gMapSelectFlags &= ~MAP_SELECT_FLAG_ENABLE_ARROW; - - auto mapCoords = FootpathBridgeGetInfoFromPos(screenCoords, &direction, &tileElement); - if (mapCoords.IsNull()) - { - return; - } - - gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE; - gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_ARROW; - gMapSelectType = MAP_SELECT_TYPE_FULL; - gMapSelectPositionA = mapCoords; - gMapSelectPositionB = mapCoords; - - int32_t z = tileElement->GetBaseZ(); - - if (tileElement->GetType() == TileElementType::Surface) - { - uint8_t slope = tileElement->AsSurface()->GetSlope(); - if (slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) - { - z += PATH_HEIGHT_STEP; - } // Add 2 for a slope - if (slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) - z += PATH_HEIGHT_STEP; // Add another 2 for a steep slope - } - - gMapSelectArrowPosition = CoordsXYZ{ mapCoords, z }; - gMapSelectArrowDirection = direction; - - map_invalidate_selection_rect(); -} - -/** - * - * rct2: 0x006A82C5 - */ -static void WindowFootpathPlacePathAtPoint(const ScreenCoordsXY& screenCoords) -{ - if (_footpathErrorOccured) - { - return; - } - - FootpathProvisionalUpdate(); - - const auto info = get_map_coordinates_from_pos( - screenCoords, EnumsToFlags(ViewportInteractionItem::Terrain, ViewportInteractionItem::Footpath)); - - if (info.SpriteType == ViewportInteractionItem::None) - { - return; - } - - // Set path - auto slope = 0; - switch (info.SpriteType) - { - case ViewportInteractionItem::Terrain: - slope = DefaultPathSlope[info.Element->AsSurface()->GetSlope() & TILE_ELEMENT_SURFACE_RAISED_CORNERS_MASK]; - break; - case ViewportInteractionItem::Footpath: - slope = info.Element->AsPath()->GetSlopeDirection(); - if (info.Element->AsPath()->IsSloped()) - { - slope |= FOOTPATH_PROPERTIES_FLAG_IS_SLOPED; - } - break; - default: - break; - } - auto z = info.Element->GetBaseZ(); - if (slope & RAISE_FOOTPATH_FLAG) - { - slope &= ~RAISE_FOOTPATH_FLAG; - z += PATH_HEIGHT_STEP; - } - - // Try and place path - auto selectedType = gFootpathSelection.GetSelectedSurface(); - PathConstructFlags constructFlags = FootpathCreateConstructFlags(selectedType); - - auto footpathPlaceAction = FootpathPlaceAction( - { info.Loc, z }, slope, selectedType, gFootpathSelection.Railings, INVALID_DIRECTION, constructFlags); - footpathPlaceAction.SetCallback([](const GameAction* ga, const GameActions::Result* result) { - if (result->Error == GameActions::Status::Ok) - { - // Don't play sound if it is no cost to prevent multiple sounds. TODO: make this work in no money scenarios - if (result->Cost != 0) - { - OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::PlaceItem, result->Position); - } - } - else - { - _footpathErrorOccured = true; - } - }); - GameActions::Execute(&footpathPlaceAction); -} - -/** - * - * rct2: 0x006A840F - */ -static void WindowFootpathStartBridgeAtPoint(const ScreenCoordsXY& screenCoords) -{ - int32_t z, direction; - TileElement* tileElement; - - auto mapCoords = FootpathBridgeGetInfoFromPos(screenCoords, &direction, &tileElement); - if (mapCoords.IsNull()) - { - return; - } - - if (tileElement->GetType() == TileElementType::Surface) - { - // If we start the path on a slope, the arrow is slightly raised, so we - // expect the path to be slightly raised as well. - uint8_t slope = tileElement->AsSurface()->GetSlope(); - z = tileElement->GetBaseZ(); - if (slope & TILE_ELEMENT_SLOPE_DOUBLE_HEIGHT) - { - // Steep diagonal slope - z += 2 * PATH_HEIGHT_STEP; - } - else if (slope & TILE_ELEMENT_SLOPE_ALL_CORNERS_UP) - { - // Normal slope - z += PATH_HEIGHT_STEP; - } - } - else - { - z = tileElement->GetBaseZ(); - if (tileElement->GetType() == TileElementType::Path) - { - if (tileElement->AsPath()->IsSloped()) - { - if (direction == (tileElement->AsPath()->GetSlopeDirection())) - { - z += PATH_HEIGHT_STEP; - } - } - } - } - - tool_cancel(); - gFootpathConstructFromPosition = { mapCoords, z }; - _footpathConstructDirection = direction; - gProvisionalFootpath.Flags = 0; - gFootpathConstructSlope = 0; - _footpathConstructionMode = PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL; - _footpathConstructValidDirections = INVALID_DIRECTION; - WindowFootpathSetEnabledAndPressedWidgets(); -} - -/** - * Construct a piece of footpath while in bridge building mode. - * rct2: 0x006A79B7 - */ -static void WindowFootpathConstruct() -{ - _window_footpath_cost = MONEY32_UNDEFINED; - FootpathProvisionalUpdate(); - - ObjectEntryIndex type; - int32_t slope; - CoordsXYZ footpathLoc; - FootpathGetNextPathInfo(&type, footpathLoc, &slope); - - PathConstructFlags constructFlags = FootpathCreateConstructFlags(type); - - auto footpathPlaceAction = FootpathPlaceAction( - footpathLoc, slope, type, gFootpathSelection.Railings, _footpathConstructDirection, constructFlags); - footpathPlaceAction.SetCallback([=](const GameAction* ga, const GameActions::Result* result) { - if (result->Error == GameActions::Status::Ok) - { - OpenRCT2::Audio::Play3D(OpenRCT2::Audio::SoundId::PlaceItem, result->Position); - - if (gFootpathConstructSlope == 0) - { - _footpathConstructValidDirections = INVALID_DIRECTION; - } - else - { - _footpathConstructValidDirections = _footpathConstructDirection; - } - - if (gFootpathGroundFlags & ELEMENT_IS_UNDERGROUND) - { - viewport_set_visibility(1); - } - - gFootpathConstructFromPosition = footpathLoc; - // If we have just built an upwards slope, the next path to construct is - // a bit higher. Note that the z returned by footpath_get_next_path_info - // already is lowered if we are building a downwards slope. - if (gFootpathConstructSlope == 2) - { - gFootpathConstructFromPosition.z += PATH_HEIGHT_STEP; - } - } - WindowFootpathSetEnabledAndPressedWidgets(); - }); - GameActions::Execute(&footpathPlaceAction); -} - -/** - * - * rct2: 0x006A78EF - */ -static void FootpathRemoveTileElement(TileElement* tileElement) -{ - auto z = tileElement->GetBaseZ(); - if (tileElement->AsPath()->IsSloped()) - { - uint8_t slopeDirection = tileElement->AsPath()->GetSlopeDirection(); - slopeDirection = DirectionReverse(slopeDirection); - if (slopeDirection == _footpathConstructDirection) - { - z += PATH_HEIGHT_STEP; - } - } - - // Find a connected edge - int32_t edge = DirectionReverse(_footpathConstructDirection); - if (!(tileElement->AsPath()->GetEdges() & (1 << edge))) - { - edge = (edge + 1) & 3; - if (!(tileElement->AsPath()->GetEdges() & (1 << edge))) - { - edge = (edge + 2) & 3; - if (!(tileElement->AsPath()->GetEdges() & (1 << edge))) - { - edge = (edge - 1) & 3; - if (!(tileElement->AsPath()->GetEdges() & (1 << edge))) - { - edge = DirectionReverse(edge); - } - } - } - } - - gFootpathConstructFromPosition.z = tileElement->GetBaseZ(); - auto action = FootpathRemoveAction(gFootpathConstructFromPosition); - GameActions::Execute(&action); - - // Move selection - edge = DirectionReverse(edge); - gFootpathConstructFromPosition.x -= CoordsDirectionDelta[edge].x; - gFootpathConstructFromPosition.y -= CoordsDirectionDelta[edge].y; - gFootpathConstructFromPosition.z = z; - _footpathConstructDirection = edge; - _footpathConstructValidDirections = INVALID_DIRECTION; -} - -/** - * - * rct2: 0x006A7873 - */ -static TileElement* FootpathGetTileElementToRemove() -{ - TileElement* tileElement; - int32_t z, zLow; - - if (!map_is_location_valid(gFootpathConstructFromPosition)) - { - return nullptr; - } - - z = std::min(255 * COORDS_Z_STEP, gFootpathConstructFromPosition.z); - zLow = z - PATH_HEIGHT_STEP; - - tileElement = map_get_first_element_at(gFootpathConstructFromPosition); - do - { - if (tileElement == nullptr) - break; - if (tileElement->GetType() == TileElementType::Path) - { - if (tileElement->GetBaseZ() == z) - { - if (tileElement->AsPath()->IsSloped()) - { - if (DirectionReverse(tileElement->AsPath()->GetSlopeDirection()) != _footpathConstructDirection) - { - continue; - } - } - - return tileElement; - } - if (tileElement->GetBaseZ() == zLow) - { - if (!tileElement->AsPath()->IsSloped()) - { - if ((tileElement->AsPath()->GetSlopeDirection()) == _footpathConstructDirection) - { - continue; - } - } - - return tileElement; - } - } - } while (!(tileElement++)->IsLastForTile()); - - return nullptr; -} - -/** - * - * rct2: 0x006A7863 - */ -static void WindowFootpathRemove() -{ - TileElement* tileElement; - - _window_footpath_cost = MONEY32_UNDEFINED; - FootpathProvisionalUpdate(); - - tileElement = FootpathGetTileElementToRemove(); - if (tileElement != nullptr) - { - FootpathRemoveTileElement(tileElement); - } - - WindowFootpathSetEnabledAndPressedWidgets(); -} - -/** - * - * rct2: 0x006A855C - */ -static void WindowFootpathSetEnabledAndPressedWidgets() -{ - rct_window* w = window_find_by_class(WindowClass::Footpath); - if (w == nullptr) - { - return; - } - - if (_footpathConstructionMode == PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL) - { - map_invalidate_map_selection_tiles(); - gMapSelectFlags |= MAP_SELECT_FLAG_ENABLE_CONSTRUCT; - gMapSelectFlags |= MAP_SELECT_FLAG_GREEN; - - int32_t direction = _footpathConstructDirection; - gMapSelectionTiles.clear(); - gMapSelectionTiles.push_back({ gFootpathConstructFromPosition.x + CoordsDirectionDelta[direction].x, - gFootpathConstructFromPosition.y + CoordsDirectionDelta[direction].y }); - map_invalidate_map_selection_tiles(); - } - - uint64_t pressedWidgets = w->pressed_widgets - & ~((1LL << WIDX_DIRECTION_NW) | (1LL << WIDX_DIRECTION_NE) | (1LL << WIDX_DIRECTION_SW) | (1LL << WIDX_DIRECTION_SE) - | (1LL << WIDX_SLOPEDOWN) | (1LL << WIDX_LEVEL) | (1LL << WIDX_SLOPEUP)); - uint64_t disabledWidgets = 0; - int32_t currentRotation = get_current_rotation(); - if (_footpathConstructionMode >= PATH_CONSTRUCTION_MODE_BRIDGE_OR_TUNNEL) - { - // Set pressed directional widget - int32_t direction = (_footpathConstructDirection + currentRotation) & 3; - pressedWidgets |= (1LL << (WIDX_DIRECTION_NW + direction)); - - // Set pressed slope widget - int32_t slope = gFootpathConstructSlope; - if (slope == TILE_ELEMENT_SLOPE_SE_SIDE_UP) - { - pressedWidgets |= (1ULL << WIDX_SLOPEDOWN); - } - else if (slope == TILE_ELEMENT_SLOPE_FLAT) - { - pressedWidgets |= (1ULL << WIDX_LEVEL); - } - else - { - pressedWidgets |= (1ULL << WIDX_SLOPEUP); - } - - // Enable / disable directional widgets - direction = _footpathConstructValidDirections; - if (direction != INVALID_DIRECTION) - { - disabledWidgets |= (1ULL << WIDX_DIRECTION_NW) | (1ULL << WIDX_DIRECTION_NE) | (1ULL << WIDX_DIRECTION_SW) - | (1ULL << WIDX_DIRECTION_SE); - - direction = (direction + currentRotation) & 3; - disabledWidgets &= ~(1 << (WIDX_DIRECTION_NW + direction)); - } - } - else - { - // Disable all bridge mode widgets - disabledWidgets |= (1ULL << WIDX_DIRECTION_GROUP) | (1ULL << WIDX_DIRECTION_NW) | (1ULL << WIDX_DIRECTION_NE) - | (1ULL << WIDX_DIRECTION_SW) | (1ULL << WIDX_DIRECTION_SE) | (1ULL << WIDX_SLOPE_GROUP) | (1ULL << WIDX_SLOPEDOWN) - | (1ULL << WIDX_LEVEL) | (1ULL << WIDX_SLOPEUP) | (1ULL << WIDX_CONSTRUCT) | (1ULL << WIDX_REMOVE); - } - - w->pressed_widgets = pressedWidgets; - w->disabled_widgets = disabledWidgets; - w->Invalidate(); -} - -/** - * - * rct2: 0x006A7B20 - */ -static void FootpathGetNextPathInfo(ObjectEntryIndex* type, CoordsXYZ& footpathLoc, int32_t* slope) -{ - auto direction = _footpathConstructDirection; - footpathLoc.x = gFootpathConstructFromPosition.x + CoordsDirectionDelta[direction].x; - footpathLoc.y = gFootpathConstructFromPosition.y + CoordsDirectionDelta[direction].y; - footpathLoc.z = gFootpathConstructFromPosition.z; - if (type != nullptr) - { - *type = gFootpathSelection.GetSelectedSurface(); - } - *slope = TILE_ELEMENT_SLOPE_FLAT; - if (gFootpathConstructSlope != 0) - { - *slope = _footpathConstructDirection | TILE_ELEMENT_SLOPE_S_CORNER_UP; - if (gFootpathConstructSlope != 2) - { - footpathLoc.z -= PATH_HEIGHT_STEP; - *slope ^= TILE_ELEMENT_SLOPE_E_CORNER_UP; - } - } -} - -static PathConstructFlags FootpathCreateConstructFlags(ObjectEntryIndex& type) -{ - PathConstructFlags pathConstructFlags = 0; - if (gFootpathSelection.IsQueueSelected) - pathConstructFlags |= PathConstructFlag::IsQueue; - if (gFootpathSelection.LegacyPath != OBJECT_ENTRY_INDEX_NULL) - { - pathConstructFlags |= PathConstructFlag::IsLegacyPathObject; - type = gFootpathSelection.LegacyPath; - } - return pathConstructFlags; -} - -void window_footpath_keyboard_shortcut_turn_left() -{ - rct_window* w = window_find_by_class(WindowClass::Footpath); - if (w == nullptr || WidgetIsDisabled(*w, WIDX_DIRECTION_NW) || WidgetIsDisabled(*w, WIDX_DIRECTION_NE) - || WidgetIsDisabled(*w, WIDX_DIRECTION_SW) || WidgetIsDisabled(*w, WIDX_DIRECTION_SE) || _footpathConstructionMode != 2) - { - return; - } - int32_t currentRotation = get_current_rotation(); - int32_t turnedRotation = _footpathConstructDirection - currentRotation + (currentRotation % 2 == 1 ? 1 : -1); - WindowFootpathMousedownDirection(turnedRotation); -} - -void window_footpath_keyboard_shortcut_turn_right() -{ - rct_window* w = window_find_by_class(WindowClass::Footpath); - if (w == nullptr || WidgetIsDisabled(*w, WIDX_DIRECTION_NW) || WidgetIsDisabled(*w, WIDX_DIRECTION_NE) - || WidgetIsDisabled(*w, WIDX_DIRECTION_SW) || WidgetIsDisabled(*w, WIDX_DIRECTION_SE) || _footpathConstructionMode != 2) - { - return; - } - int32_t currentRotation = get_current_rotation(); - int32_t turnedRotation = _footpathConstructDirection - currentRotation + (currentRotation % 2 == 1 ? -1 : 1); - WindowFootpathMousedownDirection(turnedRotation); -} - -void window_footpath_keyboard_shortcut_slope_down() -{ - rct_window* w = window_find_by_class(WindowClass::Footpath); - if (w == nullptr || WidgetIsDisabled(*w, WIDX_SLOPEDOWN) || WidgetIsDisabled(*w, WIDX_LEVEL) - || WidgetIsDisabled(*w, WIDX_SLOPEUP) || w->widgets[WIDX_LEVEL].type == WindowWidgetType::Empty) - { - return; - } - - switch (gFootpathConstructSlope) - { - case 0: - window_event_mouse_down_call(w, WIDX_SLOPEDOWN); - break; - case 2: - window_event_mouse_down_call(w, WIDX_LEVEL); - break; - default: - case 6: - return; - } -} - -void window_footpath_keyboard_shortcut_slope_up() -{ - rct_window* w = window_find_by_class(WindowClass::Footpath); - if (w == nullptr || WidgetIsDisabled(*w, WIDX_SLOPEDOWN) || WidgetIsDisabled(*w, WIDX_LEVEL) - || WidgetIsDisabled(*w, WIDX_SLOPEUP) || w->widgets[WIDX_LEVEL].type == WindowWidgetType::Empty) - { - return; - } - - switch (gFootpathConstructSlope) - { - case 6: - window_event_mouse_down_call(w, WIDX_LEVEL); - break; - case 0: - window_event_mouse_down_call(w, WIDX_SLOPEUP); - break; - default: - case 2: - return; - } -} - -void window_footpath_keyboard_shortcut_demolish_current() -{ - rct_window* w = window_find_by_class(WindowClass::Footpath); - if (w == nullptr || WidgetIsDisabled(*w, WIDX_REMOVE) || w->widgets[WIDX_REMOVE].type == WindowWidgetType::Empty - || (!gCheatsBuildInPauseMode && game_is_paused())) - { - return; - } - - WindowFootpathRemove(); -} - -void window_footpath_keyboard_shortcut_build_current() -{ - rct_window* w = window_find_by_class(WindowClass::Footpath); - if (w == nullptr || WidgetIsDisabled(*w, WIDX_CONSTRUCT) || w->widgets[WIDX_CONSTRUCT].type == WindowWidgetType::Empty) - { - return; - } - - window_event_mouse_up_call(w, WIDX_CONSTRUCT); + return WindowFocusOrCreate(WindowClass::Footpath, WW_WINDOW, WH_WINDOW, 0); } void WindowFootpathResetSelectedPath() { gFootpathSelection = {}; } + +void window_footpath_keyboard_shortcut_turn_left() +{ + rct_window* w = window_find_by_class(WindowClass::Footpath); + if (w != nullptr) + { + auto* footpathWindow = static_cast(w); + if (footpathWindow != nullptr) + { + footpathWindow->KeyboardShortcutTurnLeft(); + } + } +} + +void window_footpath_keyboard_shortcut_turn_right() +{ + rct_window* w = window_find_by_class(WindowClass::Footpath); + if (w != nullptr) + { + auto* footpathWindow = static_cast(w); + if (footpathWindow != nullptr) + { + footpathWindow->KeyboardShortcutTurnRight(); + } + } +} + +void window_footpath_keyboard_shortcut_slope_down() +{ + rct_window* w = window_find_by_class(WindowClass::Footpath); + if (w != nullptr) + { + auto* footpathWindow = static_cast(w); + if (footpathWindow != nullptr) + { + footpathWindow->KeyboardShortcutShortcutSlopeDown(); + } + } +} + +void window_footpath_keyboard_shortcut_slope_up() +{ + rct_window* w = window_find_by_class(WindowClass::Footpath); + if (w != nullptr) + { + auto* footpathWindow = static_cast(w); + if (footpathWindow != nullptr) + { + footpathWindow->KeyboardShortcutSlopeUp(); + } + } +} + +void window_footpath_keyboard_shortcut_demolish_current() +{ + rct_window* w = window_find_by_class(WindowClass::Footpath); + if (w != nullptr) + { + auto* footpathWindow = static_cast(w); + if (footpathWindow != nullptr) + { + footpathWindow->KeyboardShortcutDemolishCurrent(); + } + } +} + +void window_footpath_keyboard_shortcut_build_current() +{ + rct_window* w = window_find_by_class(WindowClass::Footpath); + if (w != nullptr) + { + auto* footpathWindow = static_cast(w); + if (footpathWindow != nullptr) + { + footpathWindow->KeyboardShortcutBuildCurrent(); + } + } +} diff --git a/src/openrct2/interface/Window_internal.h b/src/openrct2/interface/Window_internal.h index b50219ee8f..7fcefb77da 100644 --- a/src/openrct2/interface/Window_internal.h +++ b/src/openrct2/interface/Window_internal.h @@ -181,7 +181,7 @@ struct rct_window virtual void OnToolDrag(WidgetIndex widgetIndex, const ScreenCoordsXY& screenCoords) { } - virtual void OnToolUp(WidgetIndex, const ScreenCoordsXY&) + virtual void OnToolUp(WidgetIndex widgetIndex, const ScreenCoordsXY&) { } virtual void OnToolAbort(WidgetIndex widgetIndex)