From 2d32c8766c1696b7f16bf8f1fcff74c4b02b0737 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Sun, 9 Mar 2025 17:49:41 +0100 Subject: [PATCH 1/4] Draw minimap in scenario selection screen for .park based scenarios --- src/openrct2-ui/windows/ScenarioSelect.cpp | 99 +++++++++++++++++++--- 1 file changed, 86 insertions(+), 13 deletions(-) diff --git a/src/openrct2-ui/windows/ScenarioSelect.cpp b/src/openrct2-ui/windows/ScenarioSelect.cpp index 59470602a2..08bde9f6bb 100644 --- a/src/openrct2-ui/windows/ScenarioSelect.cpp +++ b/src/openrct2-ui/windows/ScenarioSelect.cpp @@ -13,15 +13,20 @@ #include #include #include +#include +#include +#include #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -31,17 +36,18 @@ namespace OpenRCT2::Ui::Windows { - static constexpr StringId kWindowTitle = STR_SELECT_SCENARIO; - static constexpr int32_t kWindowWidth = 734; - static constexpr int32_t kWindowHeight = 384; + static constexpr int32_t kInitialNumUnlockedScenarios = 5; + static constexpr uint8_t kNumTabs = 10; + static constexpr int32_t kPreviewPaneWidth = 179; static constexpr int32_t kSidebarWidth = 180; - static constexpr int32_t kTabWidth = 92; static constexpr int32_t kTabHeight = 34; - static constexpr int32_t kTrueFontSize = 24; static constexpr int32_t kWidgetsStart = 17; static constexpr int32_t kTabsStart = kWidgetsStart; - static constexpr int32_t kInitialNumUnlockedScenarios = 5; - constexpr uint8_t kNumTabs = 10; + static constexpr int32_t kTabWidth = 92; + static constexpr int32_t kTrueFontSize = 24; + static constexpr int32_t kWindowHeight = 384; + static constexpr int32_t kWindowWidth = 734; + static constexpr StringId kWindowTitle = STR_SELECT_SCENARIO; enum class ListItemType : uint8_t { @@ -117,6 +123,7 @@ namespace OpenRCT2::Ui::Windows std::function _callback; std::vector _listItems; const ScenarioIndexEntry* _highlightedScenario = nullptr; + ParkPreview _preview; public: ScenarioSelectWindow(std::function callback) @@ -150,9 +157,12 @@ namespace OpenRCT2::Ui::Windows if (widgetIndex >= WIDX_TAB1 && widgetIndex <= WIDX_TAB10) { selected_tab = widgetIndex - 4; - _highlightedScenario = nullptr; Config::Get().interface.ScenarioselectLastTab = selected_tab; Config::Save(); + + _highlightedScenario = nullptr; + _preview = {}; + InitialiseListItems(); Invalidate(); OnResize(); @@ -162,10 +172,69 @@ namespace OpenRCT2::Ui::Windows } } + void LoadPreview() + { + _preview = {}; + + if (_highlightedScenario == nullptr) + return; + + auto& path = _highlightedScenario->Path; + auto fs = FileStream(path, FileMode::open); + + ClassifiedFileInfo info; + if (!TryClassifyFile(&fs, &info) || info.Type != FileType::park) + { + // TODO: try loading a preview from a 'scenario meta' object file + return; + } + + try + { + auto& objectRepository = GetContext()->GetObjectRepository(); + auto parkImporter = ParkImporter::CreateParkFile(objectRepository); + parkImporter->LoadFromStream(&fs, false, true, path.c_str()); + _preview = parkImporter->GetParkPreview(); + } + catch (const std::exception& e) + { + LOG_ERROR("Could not get preview:", e.what()); + _preview = {}; + return; + } + } + + ScreenCoordsXY DrawPreview(DrawPixelInfo& dpi, ScreenCoordsXY screenPos) + { + // Find minimap image to draw, if available + PreviewImage* image = nullptr; + for (auto& candidate : _preview.images) + { + if (candidate.type == PreviewImageType::miniMap) + { + image = &candidate; + break; + } + } + + if (image == nullptr) + return screenPos; + + // Draw frame + auto startFrameX = width - (kPreviewPaneWidth / 2) - (image->width / 2); + auto frameStartPos = ScreenCoordsXY(windowPos.x + startFrameX, screenPos.y + 15); + auto frameEndPos = frameStartPos + ScreenCoordsXY(image->width + 1, image->height + 1); + GfxFillRectInset(dpi, { frameStartPos, frameEndPos }, colours[1], INSET_RECT_F_60 | INSET_RECT_FLAG_FILL_MID_LIGHT); + + // Draw image, if available + auto imagePos = frameStartPos + ScreenCoordsXY(1, 1); + drawPreviewImage(*image, dpi, imagePos); + + return frameEndPos; + } + void OnDraw(DrawPixelInfo& dpi) override { - const ScenarioIndexEntry* scenario; - DrawWidgets(dpi); StringId format = STR_WINDOW_COLOUR_2_STRINGID; @@ -199,7 +268,7 @@ namespace OpenRCT2::Ui::Windows } // Return if no scenario highlighted - scenario = _highlightedScenario; + auto* scenario = _highlightedScenario; if (scenario == nullptr) { if (_showLockedInformation) @@ -242,7 +311,10 @@ namespace OpenRCT2::Ui::Windows ft.Add(scenario->Name.c_str()); DrawTextEllipsised( dpi, screenPos + ScreenCoordsXY{ 85, 0 }, 170, STR_WINDOW_COLOUR_2_STRINGID, ft, { TextAlignment::CENTRE }); - screenPos.y += 15; + + // Draw preview + auto previewEnd = DrawPreview(dpi, screenPos); + screenPos.y = previewEnd.y + 15; // Scenario details ft = Formatter(); @@ -289,7 +361,7 @@ namespace OpenRCT2::Ui::Windows ResizeFrameWithPage(); const int32_t bottomMargin = Config::Get().general.DebuggingTools ? 17 : 5; - widgets[WIDX_SCENARIOLIST].right = width - 179; + widgets[WIDX_SCENARIOLIST].right = width - kPreviewPaneWidth; widgets[WIDX_SCENARIOLIST].bottom = height - bottomMargin; } @@ -353,6 +425,7 @@ namespace OpenRCT2::Ui::Windows if (_highlightedScenario != selected) { _highlightedScenario = selected; + LoadPreview(); Invalidate(); } else if (_showLockedInformation != originalShowLockedInformation) From 4e974a5711927f6e9318411b6f5f295a51d2c9b3 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Fri, 14 Mar 2025 14:47:23 +0100 Subject: [PATCH 2/4] Improve map size heuristics in generatePreviewMap --- src/openrct2/park/ParkPreview.cpp | 33 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/openrct2/park/ParkPreview.cpp b/src/openrct2/park/ParkPreview.cpp index 204fc644b5..fd51b881a9 100644 --- a/src/openrct2/park/ParkPreview.cpp +++ b/src/openrct2/park/ParkPreview.cpp @@ -115,25 +115,40 @@ namespace OpenRCT2 static std::optional generatePreviewMap() { const auto& gameState = getGameState(); - const auto previewSize = 128; - const auto longEdgeSize = std::max(gameState.mapSize.x, gameState.mapSize.y); - const auto nearestPower = Numerics::ceil2(longEdgeSize, previewSize); - const auto mapSkipFactor = nearestPower / previewSize; - const auto offset = mapSkipFactor > 0 ? (nearestPower - longEdgeSize) / mapSkipFactor : 1; + const auto drawableMapSize = TileCoordsXY{ gameState.mapSize.x - 2, gameState.mapSize.y - 2 }; + const auto longEdgeSize = std::max(drawableMapSize.x, drawableMapSize.y); + const auto idealPreviewSize = 150; + + auto longEdgeSizeLeft = longEdgeSize; + uint8_t mapSkipFactor = 1; + while (longEdgeSizeLeft > idealPreviewSize) + { + longEdgeSizeLeft -= idealPreviewSize; + mapSkipFactor++; + } + + const uint8_t previewWidth = std::max(1, drawableMapSize.x / mapSkipFactor); + const uint8_t previewHeight = std::max(1, drawableMapSize.y / mapSkipFactor); PreviewImage image{ .type = PreviewImageType::miniMap, - .width = previewSize, - .height = previewSize, + .width = previewWidth, + .height = previewHeight, }; for (auto y = 0u; y < image.height; y++) { + int32_t mapY = 1 + (y * mapSkipFactor); + if (mapY >= drawableMapSize.y) + break; + for (auto x = 0u; x < image.width; x++) { - auto pos = TileCoordsXY(gameState.mapSize.x - (x + 1) * mapSkipFactor + 1, y * mapSkipFactor + 1); + int32_t mapX = drawableMapSize.x - (x * mapSkipFactor); + if (mapX < 1) + break; - image.pixels[(y + offset) * previewSize + (x + offset)] = getPreviewColourByTilePos(pos); + image.pixels[y * image.width + x] = getPreviewColourByTilePos({ mapX, mapY }); } } From 79db8d494ad0abb94c074665cc2c62fd6deb85d5 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Thu, 20 Mar 2025 22:28:26 +0100 Subject: [PATCH 3/4] Improve surface colour match by dithering and alternating tile colours Rename colour variable to paletteIndex --- src/openrct2/park/ParkPreview.cpp | 48 +++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/openrct2/park/ParkPreview.cpp b/src/openrct2/park/ParkPreview.cpp index fd51b881a9..292559fa6f 100644 --- a/src/openrct2/park/ParkPreview.cpp +++ b/src/openrct2/park/ParkPreview.cpp @@ -51,14 +51,18 @@ namespace OpenRCT2 return preview; } + static uint8_t _tileColourIndex = 0; + static PaletteIndex getPreviewColourByTilePos(const TileCoordsXY& pos) { - PaletteIndex colour = PALETTE_INDEX_0; + PaletteIndex paletteIndex = PALETTE_INDEX_0; auto tileElement = MapGetFirstElementAt(pos); if (tileElement == nullptr) - return colour; + return paletteIndex; + PaletteIndex surfaceColour = paletteIndex; + bool isOutsidePark = false; do { switch (tileElement->GetType()) @@ -68,39 +72,42 @@ namespace OpenRCT2 auto* surfaceElement = tileElement->AsSurface(); if (surfaceElement == nullptr) { - colour = PALETTE_INDEX_0; + surfaceColour = paletteIndex = PALETTE_INDEX_0; break; } if (surfaceElement->GetWaterHeight() > 0) { - colour = PALETTE_INDEX_195; - break; + surfaceColour = paletteIndex = PALETTE_INDEX_195; + } + else + { + const auto* surfaceObject = surfaceElement->GetSurfaceObject(); + if (surfaceObject != nullptr) + { + surfaceColour = paletteIndex = surfaceObject->MapColours[_tileColourIndex]; + } } - const auto* surfaceObject = surfaceElement->GetSurfaceObject(); - if (surfaceObject != nullptr) - { - colour = surfaceObject->MapColours[1]; - } + isOutsidePark |= !(surfaceElement->GetOwnership() & OWNERSHIP_OWNED); break; } case TileElementType::Path: - colour = PALETTE_INDEX_17; + paletteIndex = PALETTE_INDEX_17; break; case TileElementType::Track: - colour = PALETTE_INDEX_183; + paletteIndex = PALETTE_INDEX_183; break; case TileElementType::SmallScenery: case TileElementType::LargeScenery: - colour = PALETTE_INDEX_99; // 64 + paletteIndex = PALETTE_INDEX_99; // 64 break; case TileElementType::Entrance: - colour = PALETTE_INDEX_186; + paletteIndex = PALETTE_INDEX_186; break; default: @@ -108,7 +115,16 @@ namespace OpenRCT2 } } while (!(tileElement++)->IsLastForTile()); - return colour; + // Darken every other tile that's outside of the park, unless it's a path + if (isOutsidePark && _tileColourIndex == 1 && paletteIndex != PALETTE_INDEX_17) + paletteIndex = PALETTE_INDEX_10; + // For rides, every other tile should use the surface colour + else if (_tileColourIndex == 1 && paletteIndex == PALETTE_INDEX_183) + paletteIndex = surfaceColour; + + _tileColourIndex = (_tileColourIndex + 1) % 2; + + return paletteIndex; } // 0x0046DB4C @@ -142,6 +158,8 @@ namespace OpenRCT2 if (mapY >= drawableMapSize.y) break; + _tileColourIndex = y % 2; + for (auto x = 0u; x < image.width; x++) { int32_t mapX = drawableMapSize.x - (x * mapSkipFactor); From 8f1978d64ac42b1eaca3f14cdeaef89b93c0b572 Mon Sep 17 00:00:00 2001 From: Aaron van Geffen Date: Thu, 27 Mar 2025 21:13:06 +0100 Subject: [PATCH 4/4] Amend changelog --- distribution/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 7454e5bf22..6505ffcd17 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,5 +1,6 @@ 0.4.21 (in development) ------------------------------------------------------------------------ +- Feature: [#22646] New scenario files now contain a minimap image, shown in the scenario selection window. - Feature: [#23774] Climates can now be customised using objects. - Feature: [#23876] New park save files now contain a preview image, shown in the load/save window. - Change: [#23932] The land rights window now checks “Land Owned” by default.