/***************************************************************************** * 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 #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 namespace OpenRCT2::Ui::Windows { static constexpr StringId WINDOW_TITLE = STR_NONE; constexpr int32_t WINDOW_SCENERY_MIN_WIDTH = 634; constexpr int32_t WINDOW_SCENERY_MIN_HEIGHT = 195; constexpr int32_t SCENERY_BUTTON_WIDTH = 66; constexpr int32_t SCENERY_BUTTON_HEIGHT = 80; constexpr int32_t InitTabPosX = 3; constexpr int32_t InitTabPosY = 17; constexpr int32_t TabWidth = 31; constexpr int32_t TabHeight = 28; constexpr int32_t ReservedTabCount = 2; constexpr int32_t MaxTabs = 257; // 255 selected tabs + misc + all constexpr int32_t MaxTabsPerRow = 20; constexpr uint8_t SceneryContentScrollIndex = 0; enum WindowSceneryListWidgetIdx { WIDX_SCENERY_BACKGROUND, WIDX_SCENERY_TITLE, WIDX_SCENERY_CLOSE, WIDX_SCENERY_TAB_CONTENT_PANEL, WIDX_SCENERY_LIST, WIDX_SCENERY_ROTATE_OBJECTS_BUTTON, WIDX_SCENERY_REPAINT_SCENERY_BUTTON, WIDX_SCENERY_PRIMARY_COLOUR_BUTTON, WIDX_SCENERY_SECONDARY_COLOUR_BUTTON, WIDX_SCENERY_TERTIARY_COLOUR_BUTTON, WIDX_SCENERY_EYEDROPPER_BUTTON, WIDX_SCENERY_BUILD_CLUSTER_BUTTON, WIDX_FILTER_TEXT_BOX, WIDX_FILTER_CLEAR_BUTTON, WIDX_RESTRICT_SCENERY, WIDX_SCENERY_TAB_1, }; validate_global_widx(WC_SCENERY, WIDX_SCENERY_TAB_1); validate_global_widx(WC_SCENERY, WIDX_SCENERY_ROTATE_OBJECTS_BUTTON); validate_global_widx(WC_SCENERY, WIDX_SCENERY_EYEDROPPER_BUTTON); // clang-format off static Widget WindowSceneryBaseWidgets[] = { WINDOW_SHIM(WINDOW_TITLE, WINDOW_SCENERY_MIN_WIDTH, WINDOW_SCENERY_MIN_HEIGHT), MakeWidget ({ 0, 43}, {634, 99}, WindowWidgetType::Resize, WindowColour::Secondary ), // 8 0x009DE2C8 MakeWidget ({ 2, 62}, {607, 80}, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_VERTICAL ), // 1000000 0x009DE418 MakeWidget ({609, 59}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_ROTATE_ARROW), STR_ROTATE_OBJECTS_90 ), // 2000000 0x009DE428 MakeWidget ({609, 83}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_PAINTBRUSH), STR_SCENERY_PAINTBRUSH_TIP ), // 4000000 0x009DE438 MakeWidget ({615, 108}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_COLOUR ), // 8000000 0x009DE448 MakeWidget ({615, 120}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_SECONDARY_COLOUR), // 10000000 0x009DE458 MakeWidget ({615, 132}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_TERTIARY_COLOUR ), // 20000000 0x009DE468 MakeWidget ({609, 145}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_G2_EYEDROPPER), STR_SCENERY_EYEDROPPER_TIP ), // 40000000 0x009DE478 MakeWidget ({609, 169}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_SCENERY_CLUSTER), STR_SCENERY_CLUSTER_TIP ), // 40000000 0x009DE478 MakeWidget ({ 4, 46}, {211, 14}, WindowWidgetType::TextBox, WindowColour::Secondary ), MakeWidget ({218, 46}, { 70, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_OBJECT_SEARCH_CLEAR ), MakeWidget ({539, 46}, { 70, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_RESTRICT_SCENERY, STR_RESTRICT_SCENERY_TIP ), kWidgetsEnd, }; // clang-format on // Persistent between window instances static size_t _activeTabIndex; static std::vector _tabSelections; uint8_t gWindowSceneryPaintEnabled; uint8_t gWindowSceneryRotation; colour_t gWindowSceneryPrimaryColour; colour_t gWindowScenerySecondaryColour; colour_t gWindowSceneryTertiaryColour; bool gWindowSceneryEyedropperEnabled; class SceneryWindow final : public Window { private: enum SceneryTabType { SCENERY_TAB_TYPE_GROUP, SCENERY_TAB_TYPE_MISC, SCENERY_TAB_TYPE_ALL, }; struct SceneryItem { int32_t allRows; int32_t selected_item; ScenerySelection scenerySelection; }; struct SceneryTabInfo { SceneryTabType Type = SCENERY_TAB_TYPE_GROUP; ObjectEntryIndex SceneryGroupIndex = OBJECT_ENTRY_INDEX_NULL; std::deque Entries{}; u8string Filter = ""; bool IsMisc() const { return Type == SCENERY_TAB_TYPE_MISC; } bool IsAll() const { return Type == SCENERY_TAB_TYPE_ALL; } bool IsSceneryGroup() const { return Type == SCENERY_TAB_TYPE_GROUP; } bool Contains(const ScenerySelection& entry) const { return std::find(std::begin(Entries), std::end(Entries), entry) != std::end(Entries); } void AddEntryToBack(const ScenerySelection& entry) { if (!Contains(entry)) { Entries.push_back(entry); } } void AddEntryToFront(const ScenerySelection& entry) { if (!Contains(entry)) { Entries.push_front(entry); } } const SceneryGroupEntry* GetSceneryGroupEntry() const { return OpenRCT2::ObjectManager::GetObjectEntry(SceneryGroupIndex); } }; std::vector _tabEntries; std::vector _widgets; int32_t _requiredWidth; int32_t _actualMinHeight; ScenerySelection _selectedScenery; int16_t _hoverCounter; SceneryTabInfo _filteredSceneryTab; public: void OnOpen() override { Init(); InitScrollWidgets(); ContentUpdateScroll(); ShowGridlines(); gWindowSceneryRotation = 3; gSceneryCtrlPressed = false; gSceneryShiftPressed = false; _selectedScenery = {}; _hoverCounter = 0; gSceneryGhostType = 0; gSceneryPlaceCost = kMoney64Undefined; gSceneryPlaceRotation = 0; gWindowSceneryPaintEnabled = 0; // repaint coloured scenery tool state gWindowSceneryEyedropperEnabled = false; width = GetRequiredWidth(); min_width = width; max_width = width; height = _actualMinHeight; min_height = height; max_height = height; if (_activeTabIndex > _tabSelections.size()) { _activeTabIndex = 0; } WindowMovePosition(*this, { ContextGetWidth() - GetRequiredWidth(), 0x1D }); WindowPushOthersBelow(*this); } void OnClose() override { SceneryRemoveGhostToolPlacement(); HideGridlines(); ViewportSetVisibility(ViewportVisibility::Default); if (gWindowSceneryScatterEnabled) WindowCloseByClass(WindowClass::SceneryScatter); if (SceneryToolIsActive()) ToolCancel(); } void OnMouseUp(WidgetIndex widgetIndex) override { switch (widgetIndex) { case WIDX_SCENERY_CLOSE: Close(); break; case WIDX_SCENERY_ROTATE_OBJECTS_BUTTON: gWindowSceneryRotation++; gWindowSceneryRotation = gWindowSceneryRotation % 4; SceneryRemoveGhostToolPlacement(); Invalidate(); break; case WIDX_SCENERY_REPAINT_SCENERY_BUTTON: gWindowSceneryPaintEnabled ^= 1; gWindowSceneryEyedropperEnabled = false; if (gWindowSceneryScatterEnabled) WindowCloseByClass(WindowClass::SceneryScatter); Invalidate(); break; case WIDX_SCENERY_EYEDROPPER_BUTTON: gWindowSceneryPaintEnabled = 0; gWindowSceneryEyedropperEnabled = !gWindowSceneryEyedropperEnabled; if (gWindowSceneryScatterEnabled) WindowCloseByClass(WindowClass::SceneryScatter); SceneryRemoveGhostToolPlacement(); Invalidate(); break; case WIDX_SCENERY_BUILD_CLUSTER_BUTTON: gWindowSceneryPaintEnabled = 0; gWindowSceneryEyedropperEnabled = false; if (gWindowSceneryScatterEnabled) WindowCloseByClass(WindowClass::SceneryScatter); else if ( NetworkGetMode() != NETWORK_MODE_CLIENT || NetworkCanPerformCommand(NetworkGetCurrentPlayerGroupIndex(), -2)) { SceneryScatterOpen(); } else { ContextShowError(STR_CANT_DO_THIS, STR_PERMISSION_DENIED, {}); } Invalidate(); break; case WIDX_FILTER_TEXT_BOX: WindowStartTextbox(*this, widgetIndex, _filteredSceneryTab.Filter, kTextInputSize); break; case WIDX_FILTER_CLEAR_BUTTON: _tabEntries[_activeTabIndex].Filter.clear(); ContentUpdateScroll(); scrolls->v_top = 0; Invalidate(); break; case WIDX_RESTRICT_SCENERY: { const auto tabIndex = _activeTabIndex; const auto tabSelectedScenery = GetSelectedScenery(tabIndex); if (!tabSelectedScenery.IsUndefined()) { const auto newStatus = !IsSceneryItemRestricted(tabSelectedScenery); const auto objectType = GetObjectTypeFromSceneryType(tabSelectedScenery.SceneryType); auto action = ScenerySetRestrictedAction(objectType, tabSelectedScenery.EntryIndex, newStatus); GameActions::Execute(&action); } break; } } } void OnResize() override { if (width < min_width) { Invalidate(); width = min_width; Invalidate(); } if (width > max_width) { Invalidate(); width = max_width; Invalidate(); } if (height < min_height) { Invalidate(); height = min_height; Invalidate(); // HACK: For some reason invalidate has not been called OnPrepareDraw(); ContentUpdateScroll(); } if (height > max_height) { Invalidate(); height = max_height; Invalidate(); // HACK: For some reason invalidate has not been called OnPrepareDraw(); ContentUpdateScroll(); } ResizeFrameWithPage(); } void OnMouseDown(WidgetIndex widgetIndex) override { switch (widgetIndex) { case WIDX_SCENERY_PRIMARY_COLOUR_BUTTON: WindowDropdownShowColour(this, &widgets[widgetIndex], colours[1], gWindowSceneryPrimaryColour); break; case WIDX_SCENERY_SECONDARY_COLOUR_BUTTON: WindowDropdownShowColour(this, &widgets[widgetIndex], colours[1], gWindowScenerySecondaryColour); break; case WIDX_SCENERY_TERTIARY_COLOUR_BUTTON: WindowDropdownShowColour(this, &widgets[widgetIndex], colours[1], gWindowSceneryTertiaryColour); break; } if (widgetIndex >= WIDX_SCENERY_TAB_1) { _activeTabIndex = widgetIndex - WIDX_SCENERY_TAB_1; Invalidate(); gSceneryPlaceCost = kMoney64Undefined; ContentUpdateScroll(); } } void OnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex) override { if (dropdownIndex == -1) return; if (widgetIndex == WIDX_SCENERY_PRIMARY_COLOUR_BUTTON) { gWindowSceneryPrimaryColour = ColourDropDownIndexToColour(dropdownIndex); } else if (widgetIndex == WIDX_SCENERY_SECONDARY_COLOUR_BUTTON) { gWindowScenerySecondaryColour = ColourDropDownIndexToColour(dropdownIndex); } else if (widgetIndex == WIDX_SCENERY_TERTIARY_COLOUR_BUTTON) { gWindowSceneryTertiaryColour = ColourDropDownIndexToColour(dropdownIndex); } Invalidate(); } void OnPeriodicUpdate() override { if (!_selectedScenery.IsUndefined()) { // Find out what scenery the cursor is over const CursorState* state = ContextGetCursorState(); WidgetIndex widgetIndex = WindowFindWidgetFromPoint(*this, state->position); if (widgetIndex == WIDX_SCENERY_LIST) { ScreenCoordsXY scrollPos = {}; int32_t scrollArea = 0; int32_t scrollId = 0; WidgetScrollGetPart(*this, &widgets[WIDX_SCENERY_LIST], state->position, scrollPos, &scrollArea, &scrollId); if (scrollArea == SCROLL_PART_VIEW) { const ScenerySelection scenery = GetSceneryIdByCursorPos(scrollPos); if (scenery == _selectedScenery) { return; } } } // Cursor was not over the currently hover selected scenery so reset hover selection. // This will happen when the mouse leaves the scroll window and is required so that the cost and description // switch to the tool scenery selection. _selectedScenery = {}; } } void OnUpdate() override { const CursorState* state = ContextGetCursorState(); WindowBase* other = WindowFindFromPoint(state->position); if (other == this) { ScreenCoordsXY window = state->position - ScreenCoordsXY{ windowPos.x - 26, windowPos.y }; if (window.y < 44 || window.x <= width) { WidgetIndex widgetIndex = WindowFindWidgetFromPoint(*this, state->position); if (widgetIndex >= WIDX_SCENERY_TAB_CONTENT_PANEL) { _hoverCounter++; if (_hoverCounter < 8) { if (InputGetState() != InputState::ScrollLeft) { min_height = _actualMinHeight; max_height = _actualMinHeight; } } else { const auto& listWidget = widgets[WIDX_SCENERY_LIST]; const auto nonListHeight = height - listWidget.height() + 12; const auto numRows = static_cast(CountRows()); const auto maxContentHeight = numRows * SCENERY_BUTTON_HEIGHT; const auto maxWindowHeight = maxContentHeight + nonListHeight; const auto windowHeight = std::clamp(maxWindowHeight, _actualMinHeight, 473); min_height = windowHeight; max_height = windowHeight; } } } } else { _hoverCounter = 0; if (InputGetState() != InputState::ScrollLeft) { min_height = _actualMinHeight; max_height = _actualMinHeight; } } if (GetCurrentTextBox().window.classification == classification && GetCurrentTextBox().window.number == number) { WindowUpdateTextboxCaret(); WidgetInvalidate(*this, WIDX_FILTER_TEXT_BOX); } Invalidate(); if (!SceneryToolIsActive()) { Close(); return; } if (gWindowSceneryEyedropperEnabled) { gCurrentToolId = Tool::Crosshair; } else if (gWindowSceneryPaintEnabled == 1) { gCurrentToolId = Tool::PaintDown; } else { const auto tabIndex = _activeTabIndex; const auto tabSelectedScenery = GetSelectedScenery(tabIndex); if (!tabSelectedScenery.IsUndefined()) { if (tabSelectedScenery.SceneryType == SCENERY_TYPE_BANNER) { gCurrentToolId = Tool::EntranceDown; } else if (tabSelectedScenery.SceneryType == SCENERY_TYPE_LARGE) { gCurrentToolId = static_cast( OpenRCT2::ObjectManager::GetObjectEntry(tabSelectedScenery.EntryIndex)->tool_id); } else if (tabSelectedScenery.SceneryType == SCENERY_TYPE_WALL) { gCurrentToolId = static_cast( OpenRCT2::ObjectManager::GetObjectEntry(tabSelectedScenery.EntryIndex)->tool_id); } else if (tabSelectedScenery.SceneryType == SCENERY_TYPE_PATH_ITEM) { gCurrentToolId = static_cast( OpenRCT2::ObjectManager::GetObjectEntry(tabSelectedScenery.EntryIndex)->tool_id); } else { // small scenery gCurrentToolId = static_cast( OpenRCT2::ObjectManager::GetObjectEntry(tabSelectedScenery.EntryIndex)->tool_id); } } else { gCurrentToolId = Tool::Arrow; } } } void OnTextInput(WidgetIndex widgetIndex, std::string_view text) override { if (widgetIndex != WIDX_FILTER_TEXT_BOX) return; if (text == _tabEntries[_activeTabIndex].Filter) return; _tabEntries[_activeTabIndex].Filter.assign(text); ContentUpdateScroll(); scrolls->v_top = 0; Invalidate(); } ScreenSize OnScrollGetSize(int32_t scrollIndex) override { if (scrollIndex == SceneryContentScrollIndex) { return ContentScrollGetSize(); } return {}; } void OnScrollMouseDown(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override { if (scrollIndex == SceneryContentScrollIndex) { ContentScrollMouseDown(screenCoords); } } void OnScrollMouseOver(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override { if (scrollIndex == SceneryContentScrollIndex) { ContentScrollMouseOver(screenCoords); } } OpenRCT2String OnTooltip(const WidgetIndex widgetIndex, const StringId fallback) override { if (widgetIndex >= WIDX_SCENERY_TAB_1) { const auto tabIndex = static_cast(widgetIndex - WIDX_SCENERY_TAB_1); if (_tabEntries.size() > tabIndex) { const auto& tabInfo = _tabEntries[tabIndex]; if (tabInfo.IsMisc()) { auto ft = Formatter(); ft.Add(STR_MISCELLANEOUS); return { fallback, ft }; } if (tabInfo.IsAll()) { auto ft = Formatter(); ft.Add(STR_ALL_SCENERY); return { fallback, ft }; } const auto* sceneryEntry = tabInfo.GetSceneryGroupEntry(); if (sceneryEntry != nullptr) { auto ft = Formatter(); ft.Add(sceneryEntry->name); return { fallback, ft }; } } } return { STR_NONE, Formatter() }; } void OnPrepareDraw() override { // Set the window title StringId titleStringId = STR_MISCELLANEOUS; const auto tabIndex = _activeTabIndex; if (tabIndex < _tabEntries.size()) { const auto& tabInfo = _tabEntries[tabIndex]; if (tabInfo.IsAll()) { titleStringId = STR_ALL_SCENERY; } else { const auto* sgEntry = tabInfo.GetSceneryGroupEntry(); if (sgEntry != nullptr) { titleStringId = sgEntry->name; } } } widgets[WIDX_SCENERY_TITLE].text = titleStringId; widgets[WIDX_FILTER_TEXT_BOX].string = _filteredSceneryTab.Filter.data(); pressed_widgets = 0; pressed_widgets |= 1uLL << (tabIndex + WIDX_SCENERY_TAB_1); if (gWindowSceneryPaintEnabled == 1) pressed_widgets |= (1uLL << WIDX_SCENERY_REPAINT_SCENERY_BUTTON); if (gWindowSceneryEyedropperEnabled) pressed_widgets |= (1uLL << WIDX_SCENERY_EYEDROPPER_BUTTON); if (gWindowSceneryScatterEnabled) pressed_widgets |= (1uLL << WIDX_SCENERY_BUILD_CLUSTER_BUTTON); widgets[WIDX_SCENERY_ROTATE_OBJECTS_BUTTON].type = WindowWidgetType::Empty; widgets[WIDX_SCENERY_BUILD_CLUSTER_BUTTON].type = WindowWidgetType::Empty; widgets[WIDX_RESTRICT_SCENERY].type = WindowWidgetType::Empty; const auto tabSelectedScenery = GetSelectedScenery(tabIndex); if (!tabSelectedScenery.IsUndefined()) { if (tabSelectedScenery.SceneryType == SCENERY_TYPE_SMALL) { widgets[WIDX_SCENERY_BUILD_CLUSTER_BUTTON].type = WindowWidgetType::FlatBtn; auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry( tabSelectedScenery.EntryIndex); if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_ROTATABLE)) { widgets[WIDX_SCENERY_ROTATE_OBJECTS_BUTTON].type = WindowWidgetType::FlatBtn; } } else if (tabSelectedScenery.SceneryType >= SCENERY_TYPE_LARGE) { widgets[WIDX_SCENERY_ROTATE_OBJECTS_BUTTON].type = WindowWidgetType::FlatBtn; } if ((gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) || GetGameState().Cheats.SandboxMode) { widgets[WIDX_RESTRICT_SCENERY].type = WindowWidgetType::Button; if (IsSceneryItemRestricted(tabSelectedScenery)) pressed_widgets |= (1uLL << WIDX_RESTRICT_SCENERY); else pressed_widgets &= ~(1uLL << WIDX_RESTRICT_SCENERY); } } widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].image = GetColourButtonImage(gWindowSceneryPrimaryColour); widgets[WIDX_SCENERY_SECONDARY_COLOUR_BUTTON].image = GetColourButtonImage(gWindowScenerySecondaryColour); widgets[WIDX_SCENERY_TERTIARY_COLOUR_BUTTON].image = GetColourButtonImage(gWindowSceneryTertiaryColour); widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].type = WindowWidgetType::Empty; widgets[WIDX_SCENERY_SECONDARY_COLOUR_BUTTON].type = WindowWidgetType::Empty; widgets[WIDX_SCENERY_TERTIARY_COLOUR_BUTTON].type = WindowWidgetType::Empty; if (gWindowSceneryPaintEnabled & 1) { // repaint coloured scenery tool is on widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; widgets[WIDX_SCENERY_SECONDARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; widgets[WIDX_SCENERY_TERTIARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; } else if (!tabSelectedScenery.IsUndefined()) { if (tabSelectedScenery.SceneryType == SCENERY_TYPE_BANNER) { auto* bannerEntry = OpenRCT2::ObjectManager::GetObjectEntry( tabSelectedScenery.EntryIndex); if (bannerEntry->flags & BANNER_ENTRY_FLAG_HAS_PRIMARY_COLOUR) { widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; } } else if (tabSelectedScenery.SceneryType == SCENERY_TYPE_LARGE) { auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry( tabSelectedScenery.EntryIndex); if (sceneryEntry->flags & LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR && !(sceneryEntry->flags & LARGE_SCENERY_FLAG_HIDE_PRIMARY_REMAP_BUTTON)) widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; if (sceneryEntry->flags & LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR && !(sceneryEntry->flags & LARGE_SCENERY_FLAG_HIDE_SECONDARY_REMAP_BUTTON)) widgets[WIDX_SCENERY_SECONDARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; if (sceneryEntry->flags & LARGE_SCENERY_FLAG_HAS_TERTIARY_COLOUR) widgets[WIDX_SCENERY_TERTIARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; } else if (tabSelectedScenery.SceneryType == SCENERY_TYPE_WALL) { auto* wallEntry = OpenRCT2::ObjectManager::GetObjectEntry(tabSelectedScenery.EntryIndex); if (wallEntry->flags & (WALL_SCENERY_HAS_PRIMARY_COLOUR | WALL_SCENERY_HAS_GLASS)) { widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; if (wallEntry->flags & WALL_SCENERY_HAS_SECONDARY_COLOUR) { widgets[WIDX_SCENERY_SECONDARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; if (wallEntry->flags2 & WALL_SCENERY_2_NO_SELECT_PRIMARY_COLOUR) widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].type = WindowWidgetType::Empty; if (wallEntry->flags & WALL_SCENERY_HAS_TERTIARY_COLOUR) widgets[WIDX_SCENERY_TERTIARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; } } } else if (tabSelectedScenery.SceneryType == SCENERY_TYPE_SMALL) { auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry( tabSelectedScenery.EntryIndex); if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR | SMALL_SCENERY_FLAG_HAS_GLASS)) { widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR)) widgets[WIDX_SCENERY_SECONDARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_TERTIARY_COLOUR)) widgets[WIDX_SCENERY_TERTIARY_COLOUR_BUTTON].type = WindowWidgetType::ColourBtn; } } } auto windowWidth = width; if (_tabEntries.size() > 0) { const auto lastTabIndex = GetMaxTabCountInARow() == MaxTabsPerRow ? MaxTabsPerRow - 1 : _tabEntries.size() - 1; const auto lastTabWidget = &widgets[WIDX_SCENERY_TAB_1 + lastTabIndex]; windowWidth = std::max(windowWidth, lastTabWidget->right + 3); if (GetSceneryTabInfoForMisc() != nullptr) { auto miscTabWidget = &widgets[WIDX_SCENERY_TAB_1 + _tabEntries.size() - 2]; miscTabWidget->left = windowWidth - 2 * TabWidth - 6; miscTabWidget->right = windowWidth - TabWidth - 7; miscTabWidget->top = InitTabPosY; miscTabWidget->bottom = InitTabPosY + TabHeight; } if (_tabEntries.back().IsAll()) { auto allTabWidget = &widgets[WIDX_SCENERY_TAB_1 + _tabEntries.size() - 1]; allTabWidget->left = windowWidth - TabWidth - 6; allTabWidget->right = windowWidth - 7; allTabWidget->top = InitTabPosY; allTabWidget->bottom = InitTabPosY + TabHeight; } } ResizeFrameWithPage(); widgets[WIDX_SCENERY_LIST].right = windowWidth - 26; widgets[WIDX_SCENERY_LIST].bottom = height - 24; widgets[WIDX_SCENERY_ROTATE_OBJECTS_BUTTON].left = windowWidth - 25; widgets[WIDX_SCENERY_REPAINT_SCENERY_BUTTON].left = windowWidth - 25; widgets[WIDX_SCENERY_EYEDROPPER_BUTTON].left = windowWidth - 25; widgets[WIDX_SCENERY_BUILD_CLUSTER_BUTTON].left = windowWidth - 25; widgets[WIDX_SCENERY_ROTATE_OBJECTS_BUTTON].right = windowWidth - 2; widgets[WIDX_SCENERY_REPAINT_SCENERY_BUTTON].right = windowWidth - 2; widgets[WIDX_SCENERY_EYEDROPPER_BUTTON].right = windowWidth - 2; widgets[WIDX_SCENERY_BUILD_CLUSTER_BUTTON].right = windowWidth - 2; widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].left = windowWidth - 19; widgets[WIDX_SCENERY_SECONDARY_COLOUR_BUTTON].left = windowWidth - 19; widgets[WIDX_SCENERY_TERTIARY_COLOUR_BUTTON].left = windowWidth - 19; widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].right = windowWidth - 8; widgets[WIDX_SCENERY_SECONDARY_COLOUR_BUTTON].right = windowWidth - 8; widgets[WIDX_SCENERY_TERTIARY_COLOUR_BUTTON].right = windowWidth - 8; } void OnDraw(DrawPixelInfo& dpi) override { DrawWidgets(dpi); DrawTabs(dpi, windowPos); auto selectedSceneryEntry = _selectedScenery; if (selectedSceneryEntry.IsUndefined()) { if (gWindowSceneryPaintEnabled & 1) // repaint coloured scenery tool is on return; if (gWindowSceneryEyedropperEnabled) return; selectedSceneryEntry = GetSelectedScenery(_activeTabIndex); if (selectedSceneryEntry.IsUndefined()) return; } auto [name, price] = GetNameAndPrice(selectedSceneryEntry); if (price != kMoney64Undefined && !(GetGameState().Park.Flags & PARK_FLAGS_NO_MONEY)) { auto ft = Formatter(); ft.Add(price); // -14 DrawTextBasic( dpi, windowPos + ScreenCoordsXY{ width - 0x1A, height - 13 }, STR_COST_LABEL, ft, { TextAlignment::RIGHT }); } auto ft = Formatter(); ft.Add(name); DrawTextEllipsised(dpi, { windowPos.x + 3, windowPos.y + height - 23 }, width - 19, STR_BLACK_STRING, ft); auto sceneryObjectType = GetObjectTypeFromSceneryType(selectedSceneryEntry.SceneryType); auto& objManager = GetContext()->GetObjectManager(); auto sceneryObject = objManager.GetLoadedObject(sceneryObjectType, selectedSceneryEntry.EntryIndex); if (sceneryObject != nullptr && sceneryObject->GetAuthors().size() > 0) { std::string authorsString; const auto& authors = sceneryObject->GetAuthors(); for (size_t i = 0; i < authors.size(); ++i) { if (i > 0) { authorsString.append(", "); } authorsString.append(authors[i]); } ft = Formatter(); ft.Add(authorsString.c_str()); DrawTextEllipsised( dpi, windowPos + ScreenCoordsXY{ 3, height - 13 }, width - 19, (sceneryObject->GetAuthors().size() == 1 ? STR_SCENERY_AUTHOR : STR_SCENERY_AUTHOR_PLURAL), ft); } } void OnScrollDraw(int32_t scrollIndex, DrawPixelInfo& dpi) override { if (scrollIndex == SceneryContentScrollIndex) { ContentScrollDraw(dpi); } } void SetSelectedItem( const ScenerySelection& scenery, const std::optional primary, const std::optional secondary, const std::optional tertiary, const std::optional rotation) { auto tabIndex = FindTabWithScenery(scenery); if (!tabIndex.has_value()) { return; } _activeTabIndex = tabIndex.value(); SetSelectedScenery(tabIndex.value(), scenery); if (primary.has_value()) { gWindowSceneryPrimaryColour = primary.value(); } if (secondary.has_value()) { gWindowScenerySecondaryColour = secondary.value(); } if (tertiary.has_value()) { gWindowSceneryTertiaryColour = tertiary.value(); } if (rotation.has_value()) { gWindowSceneryRotation = rotation.value(); } gWindowSceneryEyedropperEnabled = false; OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click1, 0, ContextGetWidth() / 2); _hoverCounter = -16; gSceneryPlaceCost = kMoney64Undefined; Invalidate(); } void SetSelectedTab(const ObjectEntryIndex sceneryGroupIndex) { const auto* tabInfo = GetSceneryTabInfoForGroup(sceneryGroupIndex); if (tabInfo == nullptr) { tabInfo = &_tabEntries.back(); } const auto tabId = std::distance(&*_tabEntries.cbegin(), tabInfo); OnMouseDown(WIDX_SCENERY_TAB_1 + tabId); } const ScenerySelection GetTabSelection() { return GetSelectedScenery(_activeTabIndex); } void Init() { _tabEntries.clear(); for (ObjectEntryIndex scenerySetIndex = 0; scenerySetIndex < MaxTabs - ReservedTabCount; scenerySetIndex++) { const auto* sceneryGroupEntry = OpenRCT2::ObjectManager::GetObjectEntry(scenerySetIndex); if (sceneryGroupEntry != nullptr && SceneryGroupIsInvented(scenerySetIndex)) { SceneryTabInfo tabInfo; tabInfo.SceneryGroupIndex = scenerySetIndex; for (const auto& sceneryEntry : sceneryGroupEntry->SceneryEntries) { if (IsSceneryAvailableToBuild(sceneryEntry)) { tabInfo.AddEntryToBack(sceneryEntry); } } if (tabInfo.Entries.size() > 0) { _tabEntries.push_back(std::move(tabInfo)); } } } // Sort scenery group tabs before adding other tabs SortTabs(); // Add misc and all tab _tabEntries.emplace_back(SceneryWindow::SceneryTabInfo{ SCENERY_TAB_TYPE_MISC }); _tabEntries.emplace_back(SceneryWindow::SceneryTabInfo{ SCENERY_TAB_TYPE_ALL }); // small scenery for (ObjectEntryIndex sceneryId = 0; sceneryId < MAX_SMALL_SCENERY_OBJECTS; sceneryId++) { const auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry(sceneryId); if (sceneryEntry != nullptr) { InitSceneryEntry({ SCENERY_TYPE_SMALL, sceneryId }, sceneryEntry->scenery_tab_id); } } // large scenery for (ObjectEntryIndex sceneryId = 0; sceneryId < MAX_LARGE_SCENERY_OBJECTS; sceneryId++) { const auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry(sceneryId); if (sceneryEntry != nullptr) { InitSceneryEntry({ SCENERY_TYPE_LARGE, sceneryId }, sceneryEntry->scenery_tab_id); } } // walls for (ObjectEntryIndex sceneryId = 0; sceneryId < MAX_WALL_SCENERY_OBJECTS; sceneryId++) { const auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry(sceneryId); if (sceneryEntry != nullptr) { InitSceneryEntry({ SCENERY_TYPE_WALL, sceneryId }, sceneryEntry->scenery_tab_id); } } // banners for (ObjectEntryIndex sceneryId = 0; sceneryId < MAX_BANNER_OBJECTS; sceneryId++) { const auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry(sceneryId); if (sceneryEntry != nullptr) { InitSceneryEntry({ SCENERY_TYPE_BANNER, sceneryId }, sceneryEntry->scenery_tab_id); } } for (ObjectEntryIndex sceneryId = 0; sceneryId < MAX_PATH_ADDITION_OBJECTS; sceneryId++) { const auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry(sceneryId); if (sceneryEntry != nullptr) { InitSceneryEntry({ SCENERY_TYPE_PATH_ITEM, sceneryId }, sceneryEntry->scenery_tab_id); } } // Remove empty tabs _tabEntries.erase( std::remove_if( _tabEntries.begin(), _tabEntries.end(), [](const SceneryTabInfo& tabInfo) { return tabInfo.Entries.empty(); }), _tabEntries.end()); // Set required width _requiredWidth = std::min(static_cast(_tabEntries.size()), MaxTabsPerRow) * TabWidth + 5; PrepareWidgets(); WindowInvalidateByClass(WindowClass::Scenery); } int32_t GetRequiredWidth() const { return std::max(_requiredWidth, WINDOW_SCENERY_MIN_WIDTH); } private: int32_t GetNumColumns() const { const auto& listWidget = widgets[WIDX_SCENERY_LIST]; const auto contentWidth = listWidget.width() - kScrollBarWidth; return contentWidth / SCENERY_BUTTON_WIDTH; } template T CountRows(T items) const { const auto rows = items / GetNumColumns(); return rows; } size_t CountRows() const { const auto tabIndex = _activeTabIndex; if (tabIndex >= _tabEntries.size()) { return 0; } const auto totalItems = _filteredSceneryTab.Entries.size(); const auto numColumns = GetNumColumns(); const auto rows = CountRows(totalItems + numColumns - 1); return rows; } int32_t constexpr ContentRowsHeight(const size_t rows) const { return static_cast(rows * SCENERY_BUTTON_HEIGHT); } void ContentUpdateScroll() { const auto tabIndex = _activeTabIndex; if (tabIndex >= _tabEntries.size()) { return; } SetFilteredScenery(tabIndex); const int32_t listHeight = height - 14 - widgets[WIDX_SCENERY_LIST].top - 1; const auto sceneryItem = ContentCountRowsWithSelectedItem(tabIndex); scrolls[SceneryContentScrollIndex].v_bottom = ContentRowsHeight(sceneryItem.allRows) + 1; const int32_t maxTop = std::max(0, scrolls[SceneryContentScrollIndex].v_bottom - listHeight); auto rowSelected = CountRows(sceneryItem.selected_item); if (sceneryItem.scenerySelection.IsUndefined()) { auto& oldScenery = GetSelectedScenery(tabIndex); if (!oldScenery.IsUndefined()) { // Make sure last selected scenery for tab is always present _filteredSceneryTab.AddEntryToFront(oldScenery); } else { // No last selection was made, use blank ScenerySelection SetSelectedScenery(tabIndex, ScenerySelection()); rowSelected = 0; if (!_filteredSceneryTab.Entries.empty()) { // Select first item in current filtered tab if available const auto& scenery = _filteredSceneryTab.Entries.front(); if (!scenery.IsUndefined()) { SetSelectedScenery(tabIndex, scenery); } } } } scrolls[SceneryContentScrollIndex].v_top = ContentRowsHeight(rowSelected); scrolls[SceneryContentScrollIndex].v_top = std::min(maxTop, scrolls[SceneryContentScrollIndex].v_top); WidgetScrollUpdateThumbs(*this, WIDX_SCENERY_LIST); } SceneryItem ContentCountRowsWithSelectedItem(const size_t tabIndex) { SceneryItem sceneryItem = { 0, 0, ScenerySelection() }; const auto scenerySelection = GetSelectedScenery(tabIndex); for (size_t i = 0; i < _filteredSceneryTab.Entries.size(); i++) { const auto& currentEntry = _filteredSceneryTab.Entries[i]; if (currentEntry == scenerySelection) { sceneryItem.selected_item = static_cast(i); sceneryItem.scenerySelection = scenerySelection; } } sceneryItem.allRows = static_cast(CountRows(_filteredSceneryTab.Entries.size() + 8)); return sceneryItem; } const ScenerySelection GetSelectedScenery(const size_t tabIndex) { if (_tabSelections.size() > tabIndex) { return _tabSelections[tabIndex]; } return {}; } void SetSelectedScenery(const size_t tabIndex, const ScenerySelection& value) { if (_tabSelections.size() <= tabIndex) { _tabSelections.resize(tabIndex + 1); } _tabSelections[tabIndex] = value; } SceneryTabInfo* GetSceneryTabInfoForMisc() { if (_tabEntries.size() >= 2) { if (_tabEntries[_tabEntries.size() - 2].IsMisc()) return &_tabEntries[_tabEntries.size() - 2]; } return nullptr; } SceneryTabInfo* GetSceneryTabInfoForAll() { if (!_tabEntries.empty()) { if (_tabEntries.back().IsAll()) return &_tabEntries.back(); } return nullptr; } SceneryTabInfo* GetSceneryTabInfoForGroup(const ObjectEntryIndex sceneryGroupIndex) { for (auto& tabEntry : _tabEntries) { if (tabEntry.SceneryGroupIndex == sceneryGroupIndex) return &tabEntry; } return nullptr; } std::optional FindTabWithScenery(const ScenerySelection& scenery) { for (size_t i = 0; i < _tabEntries.size(); i++) { const auto& tabInfo = _tabEntries[i]; if (tabInfo.IsAll()) { // The scenery will be always added here so exclude this one. continue; } if (tabInfo.Contains(scenery)) { return i; } } return std::nullopt; } void InitSceneryEntry(const ScenerySelection& selection, const ObjectEntryIndex sceneryGroupIndex) { Guard::ArgumentInRange(selection.EntryIndex, 0, OBJECT_ENTRY_INDEX_NULL); if (IsSceneryAvailableToBuild(selection)) { // Add scenery to all tab auto* allTabInfo = GetSceneryTabInfoForAll(); if (allTabInfo != nullptr) { allTabInfo->AddEntryToBack(selection); } // Add scenery to primary group (usually trees or path additions) if (sceneryGroupIndex != OBJECT_ENTRY_INDEX_NULL) { auto* tabInfo = GetSceneryTabInfoForGroup(sceneryGroupIndex); if (tabInfo != nullptr) { tabInfo->AddEntryToBack(selection); return; } } // If scenery has no tab, add it to misc const auto tabIndex = FindTabWithScenery(selection); if (!tabIndex.has_value()) { auto* tabInfo = GetSceneryTabInfoForMisc(); if (tabInfo != nullptr) { tabInfo->AddEntryToBack(selection); } } } } void SetFilteredScenery(const size_t tabIndex) { auto currentTab = _tabEntries[tabIndex]; _filteredSceneryTab.Entries.clear(); _filteredSceneryTab.Filter = currentTab.Filter; for (auto selection : currentTab.Entries) { if (MatchFilter(selection)) _filteredSceneryTab.AddEntryToBack(selection); } } bool MatchFilter(const ScenerySelection& selection) { if (_filteredSceneryTab.Filter.empty()) return true; auto& objManager = GetContext()->GetObjectManager(); auto sceneryObjectType = GetObjectTypeFromSceneryType(selection.SceneryType); auto sceneryObject = objManager.GetLoadedObject(sceneryObjectType, selection.EntryIndex); return IsFilterInName(*sceneryObject) || IsFilterInAuthors(*sceneryObject) || IsFilterInIdentifier(*sceneryObject) || IsFilterInFilename(*sceneryObject); } bool IsFilterInName(const Object& object) { return String::Contains(object.GetName(), _filteredSceneryTab.Filter, true); } bool IsFilterInAuthors(const Object& object) { for (auto author : object.GetAuthors()) if (String::Contains(author, _filteredSceneryTab.Filter, true)) return true; return false; } bool IsFilterInIdentifier(const Object& object) { return String::Contains(object.GetIdentifier(), _filteredSceneryTab.Filter, true); } bool IsFilterInFilename(const Object& object) { auto repoItem = ObjectRepositoryFindObjectByEntry(&(object.GetObjectEntry())); return String::Contains(repoItem->Path, _filteredSceneryTab.Filter, true); } void SortTabs() { std::sort(_tabEntries.begin(), _tabEntries.end(), [](const SceneryTabInfo& a, const SceneryTabInfo& b) { if (a.SceneryGroupIndex == b.SceneryGroupIndex) return false; if (a.SceneryGroupIndex == OBJECT_ENTRY_INDEX_NULL) return false; if (b.SceneryGroupIndex == OBJECT_ENTRY_INDEX_NULL) return true; const auto* entryA = a.GetSceneryGroupEntry(); const auto* entryB = b.GetSceneryGroupEntry(); return entryA->priority < entryB->priority; }); } int32_t GetTabRowCount() { return std::max((static_cast(_tabEntries.size()) + MaxTabsPerRow - 1) / MaxTabsPerRow, 0); } int32_t GetMaxTabCountInARow() { int32_t tabEntries = static_cast(_tabEntries.size()); return std::min(tabEntries, MaxTabsPerRow); } void PrepareWidgets() { // Add the base widgets _widgets.clear(); for (const auto& widget : WindowSceneryBaseWidgets) { _widgets.push_back(widget); } // Remove WWT_LAST auto lastWidget = _widgets.back(); _widgets.pop_back(); // Add tabs _actualMinHeight = WINDOW_SCENERY_MIN_HEIGHT; int32_t xInit = InitTabPosX; int32_t tabsInThisRow = 0; auto hasMisc = GetSceneryTabInfoForMisc() != nullptr; auto maxTabsInThisRow = MaxTabsPerRow - 1 - (hasMisc ? 1 : 0); ScreenCoordsXY pos = { xInit, InitTabPosY }; for (const auto& tabInfo : _tabEntries) { auto widget = MakeTab(pos, STR_STRING_DEFINED_TOOLTIP); pos.x += TabWidth; if (tabInfo.IsMisc()) { widget.image = ImageId(SPR_TAB_QUESTION, FilterPaletteID::PaletteNull); } else if (tabInfo.IsAll()) { widget.image = ImageId(SPR_TAB, FilterPaletteID::PaletteNull); } else if (tabInfo.IsSceneryGroup()) { // Default tab image widget.image = ImageId(SPR_TAB_QUESTION, FilterPaletteID::PaletteNull); // Scenery Group image auto scgEntry = tabInfo.GetSceneryGroupEntry(); if (scgEntry != nullptr) { widget.image = ImageId(scgEntry->image, colours[1]); } } _widgets.push_back(widget); tabsInThisRow++; if (tabsInThisRow >= maxTabsInThisRow) { pos.x = xInit; pos.y += TabHeight; tabsInThisRow = 0; _actualMinHeight += TabHeight; maxTabsInThisRow = MaxTabsPerRow; } } _widgets.push_back(lastWidget); // Shift base widgets based on number of tab rows int32_t shiftAmount = (GetTabRowCount() - 1) * TabHeight; if (shiftAmount > 0) { _widgets[WIDX_SCENERY_LIST].top += shiftAmount; _widgets[WIDX_SCENERY_LIST].bottom += shiftAmount; _widgets[WIDX_SCENERY_ROTATE_OBJECTS_BUTTON].top += shiftAmount; _widgets[WIDX_SCENERY_ROTATE_OBJECTS_BUTTON].bottom += shiftAmount; _widgets[WIDX_SCENERY_REPAINT_SCENERY_BUTTON].top += shiftAmount; _widgets[WIDX_SCENERY_REPAINT_SCENERY_BUTTON].bottom += shiftAmount; _widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].top += shiftAmount; _widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].bottom += shiftAmount; _widgets[WIDX_SCENERY_SECONDARY_COLOUR_BUTTON].top += shiftAmount; _widgets[WIDX_SCENERY_SECONDARY_COLOUR_BUTTON].bottom += shiftAmount; _widgets[WIDX_SCENERY_TERTIARY_COLOUR_BUTTON].top += shiftAmount; _widgets[WIDX_SCENERY_TERTIARY_COLOUR_BUTTON].bottom += shiftAmount; _widgets[WIDX_SCENERY_EYEDROPPER_BUTTON].top += shiftAmount; _widgets[WIDX_SCENERY_EYEDROPPER_BUTTON].bottom += shiftAmount; _widgets[WIDX_SCENERY_BUILD_CLUSTER_BUTTON].top += shiftAmount; _widgets[WIDX_SCENERY_BUILD_CLUSTER_BUTTON].bottom += shiftAmount; _widgets[WIDX_FILTER_TEXT_BOX].top += shiftAmount; _widgets[WIDX_FILTER_TEXT_BOX].bottom += shiftAmount; _widgets[WIDX_FILTER_CLEAR_BUTTON].top += shiftAmount; _widgets[WIDX_FILTER_CLEAR_BUTTON].bottom += shiftAmount; } widgets = _widgets.data(); } ScenerySelection GetSceneryIdByCursorPos(const ScreenCoordsXY& screenCoords) const { ScenerySelection scenery{}; const auto numColumns = GetNumColumns(); const auto colIndex = screenCoords.x / SCENERY_BUTTON_WIDTH; const auto rowIndex = screenCoords.y / SCENERY_BUTTON_HEIGHT; if (colIndex >= 0 && colIndex < numColumns && rowIndex >= 0) { const auto tabSceneryIndex = static_cast((rowIndex * numColumns) + colIndex); if (tabSceneryIndex < _filteredSceneryTab.Entries.size()) { return _filteredSceneryTab.Entries[tabSceneryIndex]; } } return scenery; } ScreenSize ContentScrollGetSize() const { auto rows = CountRows(); return { 0, ContentRowsHeight(rows) }; } void ContentScrollMouseDown(const ScreenCoordsXY& screenCoords) { const auto scenery = GetSceneryIdByCursorPos(screenCoords); if (scenery.IsUndefined()) return; auto lastScenery = GetSelectedScenery(_activeTabIndex); if (lastScenery != scenery && !MatchFilter(lastScenery)) { _filteredSceneryTab.Entries.pop_front(); } SetSelectedScenery(_activeTabIndex, scenery); gWindowSceneryPaintEnabled &= 0xFE; gWindowSceneryEyedropperEnabled = false; OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click1, 0, windowPos.x + (width / 2)); _hoverCounter = -16; gSceneryPlaceCost = kMoney64Undefined; Invalidate(); } void ContentScrollMouseOver(const ScreenCoordsXY& screenCoords) { ScenerySelection scenery = GetSceneryIdByCursorPos(screenCoords); if (!scenery.IsUndefined()) { _selectedScenery = scenery; Invalidate(); } } std::pair GetNameAndPrice(ScenerySelection selectedScenery) { StringId name = STR_UNKNOWN_OBJECT_TYPE; money64 price = kMoney64Undefined; if (selectedScenery.IsUndefined() && gSceneryPlaceCost != kMoney64Undefined) { price = gSceneryPlaceCost; } else { switch (selectedScenery.SceneryType) { case SCENERY_TYPE_SMALL: { auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry( selectedScenery.EntryIndex); if (sceneryEntry != nullptr) { price = sceneryEntry->price; name = sceneryEntry->name; } break; } case SCENERY_TYPE_PATH_ITEM: { auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry( selectedScenery.EntryIndex); if (sceneryEntry != nullptr) { price = sceneryEntry->price; name = sceneryEntry->name; } break; } case SCENERY_TYPE_WALL: { auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry( selectedScenery.EntryIndex); if (sceneryEntry != nullptr) { price = sceneryEntry->price; name = sceneryEntry->name; } break; } case SCENERY_TYPE_LARGE: { auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry( selectedScenery.EntryIndex); if (sceneryEntry != nullptr) { price = sceneryEntry->price; name = sceneryEntry->name; } break; } case SCENERY_TYPE_BANNER: { auto* sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry( selectedScenery.EntryIndex); if (sceneryEntry != nullptr) { price = sceneryEntry->price; name = sceneryEntry->name; } break; } } } return { name, price }; } void DrawTabs(DrawPixelInfo& dpi, const ScreenCoordsXY& offset) { for (size_t tabIndex = 0; tabIndex < _tabEntries.size(); tabIndex++) { auto widgetIndex = static_cast(WIDX_SCENERY_TAB_1 + tabIndex); auto widgetCoordsXY = ScreenCoordsXY(widgets[widgetIndex].left, widgets[widgetIndex].top); if (_tabEntries[tabIndex].IsAll()) { auto imageId = ImageId(SPR_G2_INFINITY, FilterPaletteID::PaletteNull); GfxDrawSprite(dpi, imageId, offset + widgetCoordsXY + ScreenCoordsXY(2, 6)); } } } void DrawSceneryItem(DrawPixelInfo& dpi, ScenerySelection scenerySelection) { if (scenerySelection.SceneryType == SCENERY_TYPE_BANNER) { auto bannerEntry = OpenRCT2::ObjectManager::GetObjectEntry(scenerySelection.EntryIndex); auto imageId = ImageId(bannerEntry->image + gWindowSceneryRotation * 2, gWindowSceneryPrimaryColour); GfxDrawSprite(dpi, imageId, { 33, 40 }); GfxDrawSprite(dpi, imageId.WithIndexOffset(1), { 33, 40 }); } else if (scenerySelection.SceneryType == SCENERY_TYPE_LARGE) { auto sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry(scenerySelection.EntryIndex); auto imageId = ImageId(sceneryEntry->image + gWindowSceneryRotation); if (sceneryEntry->flags & LARGE_SCENERY_FLAG_HAS_PRIMARY_COLOUR) imageId = imageId.WithPrimary(gWindowSceneryPrimaryColour); if (sceneryEntry->flags & LARGE_SCENERY_FLAG_HAS_SECONDARY_COLOUR) imageId = imageId.WithSecondary(gWindowScenerySecondaryColour); if (sceneryEntry->flags & LARGE_SCENERY_FLAG_HAS_TERTIARY_COLOUR) imageId = imageId.WithTertiary(gWindowSceneryTertiaryColour); GfxDrawSprite(dpi, imageId, { 33, 0 }); } else if (scenerySelection.SceneryType == SCENERY_TYPE_WALL) { auto wallEntry = OpenRCT2::ObjectManager::GetObjectEntry(scenerySelection.EntryIndex); auto imageId = ImageId(wallEntry->image); auto spriteTop = (wallEntry->height * 2) + 0x32; if (wallEntry->flags & WALL_SCENERY_HAS_GLASS) { imageId = imageId.WithPrimary(gWindowSceneryPrimaryColour); if (wallEntry->flags & WALL_SCENERY_HAS_SECONDARY_COLOUR) { imageId = imageId.WithSecondary(gWindowScenerySecondaryColour); } GfxDrawSprite(dpi, imageId, { 47, spriteTop }); auto glassImageId = ImageId(wallEntry->image + 6).WithTransparency(gWindowSceneryPrimaryColour); GfxDrawSprite(dpi, glassImageId, { 47, spriteTop }); } else { imageId = imageId.WithPrimary(gWindowSceneryPrimaryColour); if (wallEntry->flags & WALL_SCENERY_HAS_SECONDARY_COLOUR) { imageId = imageId.WithSecondary(gWindowScenerySecondaryColour); if (wallEntry->flags & WALL_SCENERY_HAS_TERTIARY_COLOUR) { imageId = imageId.WithTertiary(gWindowSceneryTertiaryColour); } } GfxDrawSprite(dpi, imageId, { 47, spriteTop }); if (wallEntry->flags & WALL_SCENERY_IS_DOOR) { GfxDrawSprite(dpi, imageId.WithIndexOffset(1), { 47, spriteTop }); } } } else if (scenerySelection.SceneryType == SCENERY_TYPE_PATH_ITEM) { auto* pathAdditionEntry = OpenRCT2::ObjectManager::GetObjectEntry( scenerySelection.EntryIndex); auto imageId = ImageId(pathAdditionEntry->image); GfxDrawSprite(dpi, imageId, { 11, 16 }); } else { auto sceneryEntry = OpenRCT2::ObjectManager::GetObjectEntry(scenerySelection.EntryIndex); auto imageId = ImageId(sceneryEntry->image + gWindowSceneryRotation); if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR)) { imageId = imageId.WithPrimary(gWindowSceneryPrimaryColour); if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR)) { imageId = imageId.WithSecondary(gWindowScenerySecondaryColour); } } if (sceneryEntry->flags & SMALL_SCENERY_FLAG_HAS_TERTIARY_COLOUR) { imageId = imageId.WithTertiary(gWindowSceneryTertiaryColour); } auto spriteTop = (sceneryEntry->height / 4) + 43; if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_FULL_TILE) && sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_VOFFSET_CENTRE)) { spriteTop -= 12; } GfxDrawSprite(dpi, imageId, { 32, spriteTop }); if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_HAS_GLASS)) { imageId = ImageId(sceneryEntry->image + 4 + gWindowSceneryRotation) .WithTransparency(gWindowSceneryPrimaryColour); GfxDrawSprite(dpi, imageId, { 32, spriteTop }); } if (sceneryEntry->HasFlag(SMALL_SCENERY_FLAG_ANIMATED_FG)) { imageId = ImageId(sceneryEntry->image + 4 + gWindowSceneryRotation); GfxDrawSprite(dpi, imageId, { 32, spriteTop }); } } } void ContentScrollDraw(DrawPixelInfo& dpi) { GfxClear(dpi, ColourMapA[colours[1]].mid_light); auto numColumns = GetNumColumns(); auto tabIndex = _activeTabIndex; if (tabIndex >= _tabEntries.size()) { return; } ScreenCoordsXY topLeft{ 0, 0 }; for (size_t sceneryTabItemIndex = 0; sceneryTabItemIndex < _filteredSceneryTab.Entries.size(); sceneryTabItemIndex++) { const auto& currentSceneryGlobal = _filteredSceneryTab.Entries[sceneryTabItemIndex]; const auto tabSelectedScenery = GetSelectedScenery(tabIndex); if (gWindowSceneryPaintEnabled == 1 || gWindowSceneryEyedropperEnabled) { if (_selectedScenery == currentSceneryGlobal) { GfxFillRectInset( dpi, { topLeft, topLeft + ScreenCoordsXY{ SCENERY_BUTTON_WIDTH - 1, SCENERY_BUTTON_HEIGHT - 1 } }, colours[1], INSET_RECT_FLAG_FILL_MID_LIGHT); } } else { if (tabSelectedScenery == currentSceneryGlobal) { GfxFillRectInset( dpi, { topLeft, topLeft + ScreenCoordsXY{ SCENERY_BUTTON_WIDTH - 1, SCENERY_BUTTON_HEIGHT - 1 } }, colours[1], (INSET_RECT_FLAG_BORDER_INSET | INSET_RECT_FLAG_FILL_MID_LIGHT)); } else if (_selectedScenery == currentSceneryGlobal) { GfxFillRectInset( dpi, { topLeft, topLeft + ScreenCoordsXY{ SCENERY_BUTTON_WIDTH - 1, SCENERY_BUTTON_HEIGHT - 1 } }, colours[1], INSET_RECT_FLAG_FILL_MID_LIGHT); } } DrawPixelInfo clipdpi; if (ClipDrawPixelInfo( clipdpi, dpi, topLeft + ScreenCoordsXY{ 1, 1 }, SCENERY_BUTTON_WIDTH - 2, SCENERY_BUTTON_HEIGHT - 2)) { DrawSceneryItem(clipdpi, currentSceneryGlobal); } topLeft.x += SCENERY_BUTTON_WIDTH; if (topLeft.x >= numColumns * SCENERY_BUTTON_WIDTH) { topLeft.y += SCENERY_BUTTON_HEIGHT; topLeft.x = 0; } } } }; WindowBase* SceneryOpen() { auto* w = static_cast(WindowBringToFrontByClass(WindowClass::Scenery)); if (w == nullptr) { w = WindowCreate(WindowClass::Scenery); } return w; } void WindowScenerySetSelectedItem( const ScenerySelection& scenery, const std::optional primary, const std::optional secondary, const std::optional tertiary, const std::optional rotation) { auto* w = static_cast(WindowBringToFrontByClass(WindowClass::Scenery)); if (w != nullptr) { w->SetSelectedItem(scenery, primary, secondary, tertiary, rotation); } } void WindowScenerySetSelectedTab(const ObjectEntryIndex sceneryGroupIndex) { // Should this bring to front? auto* w = static_cast(WindowFindByClass(WindowClass::Scenery)); if (w != nullptr) { return w->SetSelectedTab(sceneryGroupIndex); } } // Used after removing objects, in order to avoid crashes. void WindowSceneryResetSelectedSceneryItems() { _tabSelections.clear(); _activeTabIndex = 0; } void WindowScenerySetDefaultPlacementConfiguration() { gWindowSceneryRotation = 3; gWindowSceneryPrimaryColour = COLOUR_BORDEAUX_RED; gWindowScenerySecondaryColour = COLOUR_YELLOW; gWindowSceneryTertiaryColour = COLOUR_DARK_BROWN; WindowSceneryResetSelectedSceneryItems(); } const ScenerySelection WindowSceneryGetTabSelection() { auto* w = static_cast(WindowFindByClass(WindowClass::Scenery)); if (w != nullptr) { return w->GetTabSelection(); } else { return {}; } } void WindowSceneryInit() { auto* w = static_cast(WindowFindByClass(WindowClass::Scenery)); if (w != nullptr) { w->Init(); } } } // namespace OpenRCT2::Ui::Windows