mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-13 02:52:35 +01:00
Merge pull request #22646 from AaronVanGeffen/scenario-preview
Show minimap in scenario list for new .park scenarios
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
0.4.21 (in development)
|
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: [#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.
|
- 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.
|
- Change: [#23932] The land rights window now checks “Land Owned” by default.
|
||||||
|
|||||||
@@ -13,15 +13,20 @@
|
|||||||
#include <openrct2-ui/interface/Widget.h>
|
#include <openrct2-ui/interface/Widget.h>
|
||||||
#include <openrct2-ui/windows/Windows.h>
|
#include <openrct2-ui/windows/Windows.h>
|
||||||
#include <openrct2/Context.h>
|
#include <openrct2/Context.h>
|
||||||
|
#include <openrct2/Diagnostic.h>
|
||||||
|
#include <openrct2/FileClassifier.h>
|
||||||
|
#include <openrct2/ParkImporter.h>
|
||||||
#include <openrct2/SpriteIds.h>
|
#include <openrct2/SpriteIds.h>
|
||||||
#include <openrct2/audio/Audio.h>
|
#include <openrct2/audio/Audio.h>
|
||||||
#include <openrct2/config/Config.h>
|
#include <openrct2/config/Config.h>
|
||||||
|
#include <openrct2/core/FileStream.h>
|
||||||
#include <openrct2/core/String.hpp>
|
#include <openrct2/core/String.hpp>
|
||||||
#include <openrct2/drawing/Drawing.h>
|
#include <openrct2/drawing/Drawing.h>
|
||||||
#include <openrct2/localisation/Formatter.h>
|
#include <openrct2/localisation/Formatter.h>
|
||||||
#include <openrct2/localisation/Formatting.h>
|
#include <openrct2/localisation/Formatting.h>
|
||||||
#include <openrct2/localisation/Localisation.Date.h>
|
#include <openrct2/localisation/Localisation.Date.h>
|
||||||
#include <openrct2/localisation/LocalisationService.h>
|
#include <openrct2/localisation/LocalisationService.h>
|
||||||
|
#include <openrct2/park/ParkPreview.h>
|
||||||
#include <openrct2/ride/RideData.h>
|
#include <openrct2/ride/RideData.h>
|
||||||
#include <openrct2/scenario/Scenario.h>
|
#include <openrct2/scenario/Scenario.h>
|
||||||
#include <openrct2/scenario/ScenarioRepository.h>
|
#include <openrct2/scenario/ScenarioRepository.h>
|
||||||
@@ -31,17 +36,18 @@
|
|||||||
|
|
||||||
namespace OpenRCT2::Ui::Windows
|
namespace OpenRCT2::Ui::Windows
|
||||||
{
|
{
|
||||||
static constexpr StringId kWindowTitle = STR_SELECT_SCENARIO;
|
static constexpr int32_t kInitialNumUnlockedScenarios = 5;
|
||||||
static constexpr int32_t kWindowWidth = 734;
|
static constexpr uint8_t kNumTabs = 10;
|
||||||
static constexpr int32_t kWindowHeight = 384;
|
static constexpr int32_t kPreviewPaneWidth = 179;
|
||||||
static constexpr int32_t kSidebarWidth = 180;
|
static constexpr int32_t kSidebarWidth = 180;
|
||||||
static constexpr int32_t kTabWidth = 92;
|
|
||||||
static constexpr int32_t kTabHeight = 34;
|
static constexpr int32_t kTabHeight = 34;
|
||||||
static constexpr int32_t kTrueFontSize = 24;
|
|
||||||
static constexpr int32_t kWidgetsStart = 17;
|
static constexpr int32_t kWidgetsStart = 17;
|
||||||
static constexpr int32_t kTabsStart = kWidgetsStart;
|
static constexpr int32_t kTabsStart = kWidgetsStart;
|
||||||
static constexpr int32_t kInitialNumUnlockedScenarios = 5;
|
static constexpr int32_t kTabWidth = 92;
|
||||||
constexpr uint8_t kNumTabs = 10;
|
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
|
enum class ListItemType : uint8_t
|
||||||
{
|
{
|
||||||
@@ -117,6 +123,7 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
std::function<void(std::string_view)> _callback;
|
std::function<void(std::string_view)> _callback;
|
||||||
std::vector<ScenarioListItem> _listItems;
|
std::vector<ScenarioListItem> _listItems;
|
||||||
const ScenarioIndexEntry* _highlightedScenario = nullptr;
|
const ScenarioIndexEntry* _highlightedScenario = nullptr;
|
||||||
|
ParkPreview _preview;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ScenarioSelectWindow(std::function<void(std::string_view)> callback)
|
ScenarioSelectWindow(std::function<void(std::string_view)> callback)
|
||||||
@@ -150,9 +157,12 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
if (widgetIndex >= WIDX_TAB1 && widgetIndex <= WIDX_TAB10)
|
if (widgetIndex >= WIDX_TAB1 && widgetIndex <= WIDX_TAB10)
|
||||||
{
|
{
|
||||||
selected_tab = widgetIndex - 4;
|
selected_tab = widgetIndex - 4;
|
||||||
_highlightedScenario = nullptr;
|
|
||||||
Config::Get().interface.ScenarioselectLastTab = selected_tab;
|
Config::Get().interface.ScenarioselectLastTab = selected_tab;
|
||||||
Config::Save();
|
Config::Save();
|
||||||
|
|
||||||
|
_highlightedScenario = nullptr;
|
||||||
|
_preview = {};
|
||||||
|
|
||||||
InitialiseListItems();
|
InitialiseListItems();
|
||||||
Invalidate();
|
Invalidate();
|
||||||
OnResize();
|
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
|
void OnDraw(DrawPixelInfo& dpi) override
|
||||||
{
|
{
|
||||||
const ScenarioIndexEntry* scenario;
|
|
||||||
|
|
||||||
DrawWidgets(dpi);
|
DrawWidgets(dpi);
|
||||||
|
|
||||||
StringId format = STR_WINDOW_COLOUR_2_STRINGID;
|
StringId format = STR_WINDOW_COLOUR_2_STRINGID;
|
||||||
@@ -199,7 +268,7 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return if no scenario highlighted
|
// Return if no scenario highlighted
|
||||||
scenario = _highlightedScenario;
|
auto* scenario = _highlightedScenario;
|
||||||
if (scenario == nullptr)
|
if (scenario == nullptr)
|
||||||
{
|
{
|
||||||
if (_showLockedInformation)
|
if (_showLockedInformation)
|
||||||
@@ -242,7 +311,10 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
ft.Add<const char*>(scenario->Name.c_str());
|
ft.Add<const char*>(scenario->Name.c_str());
|
||||||
DrawTextEllipsised(
|
DrawTextEllipsised(
|
||||||
dpi, screenPos + ScreenCoordsXY{ 85, 0 }, 170, STR_WINDOW_COLOUR_2_STRINGID, ft, { TextAlignment::CENTRE });
|
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
|
// Scenario details
|
||||||
ft = Formatter();
|
ft = Formatter();
|
||||||
@@ -289,7 +361,7 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
|
|
||||||
ResizeFrameWithPage();
|
ResizeFrameWithPage();
|
||||||
const int32_t bottomMargin = Config::Get().general.DebuggingTools ? 17 : 5;
|
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;
|
widgets[WIDX_SCENARIOLIST].bottom = height - bottomMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,6 +425,7 @@ namespace OpenRCT2::Ui::Windows
|
|||||||
if (_highlightedScenario != selected)
|
if (_highlightedScenario != selected)
|
||||||
{
|
{
|
||||||
_highlightedScenario = selected;
|
_highlightedScenario = selected;
|
||||||
|
LoadPreview();
|
||||||
Invalidate();
|
Invalidate();
|
||||||
}
|
}
|
||||||
else if (_showLockedInformation != originalShowLockedInformation)
|
else if (_showLockedInformation != originalShowLockedInformation)
|
||||||
|
|||||||
@@ -51,14 +51,18 @@ namespace OpenRCT2
|
|||||||
return preview;
|
return preview;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint8_t _tileColourIndex = 0;
|
||||||
|
|
||||||
static PaletteIndex getPreviewColourByTilePos(const TileCoordsXY& pos)
|
static PaletteIndex getPreviewColourByTilePos(const TileCoordsXY& pos)
|
||||||
{
|
{
|
||||||
PaletteIndex colour = PALETTE_INDEX_0;
|
PaletteIndex paletteIndex = PALETTE_INDEX_0;
|
||||||
|
|
||||||
auto tileElement = MapGetFirstElementAt(pos);
|
auto tileElement = MapGetFirstElementAt(pos);
|
||||||
if (tileElement == nullptr)
|
if (tileElement == nullptr)
|
||||||
return colour;
|
return paletteIndex;
|
||||||
|
|
||||||
|
PaletteIndex surfaceColour = paletteIndex;
|
||||||
|
bool isOutsidePark = false;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
switch (tileElement->GetType())
|
switch (tileElement->GetType())
|
||||||
@@ -68,39 +72,42 @@ namespace OpenRCT2
|
|||||||
auto* surfaceElement = tileElement->AsSurface();
|
auto* surfaceElement = tileElement->AsSurface();
|
||||||
if (surfaceElement == nullptr)
|
if (surfaceElement == nullptr)
|
||||||
{
|
{
|
||||||
colour = PALETTE_INDEX_0;
|
surfaceColour = paletteIndex = PALETTE_INDEX_0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (surfaceElement->GetWaterHeight() > 0)
|
if (surfaceElement->GetWaterHeight() > 0)
|
||||||
{
|
{
|
||||||
colour = PALETTE_INDEX_195;
|
surfaceColour = paletteIndex = PALETTE_INDEX_195;
|
||||||
break;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto* surfaceObject = surfaceElement->GetSurfaceObject();
|
||||||
|
if (surfaceObject != nullptr)
|
||||||
|
{
|
||||||
|
surfaceColour = paletteIndex = surfaceObject->MapColours[_tileColourIndex];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto* surfaceObject = surfaceElement->GetSurfaceObject();
|
isOutsidePark |= !(surfaceElement->GetOwnership() & OWNERSHIP_OWNED);
|
||||||
if (surfaceObject != nullptr)
|
|
||||||
{
|
|
||||||
colour = surfaceObject->MapColours[1];
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case TileElementType::Path:
|
case TileElementType::Path:
|
||||||
colour = PALETTE_INDEX_17;
|
paletteIndex = PALETTE_INDEX_17;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TileElementType::Track:
|
case TileElementType::Track:
|
||||||
colour = PALETTE_INDEX_183;
|
paletteIndex = PALETTE_INDEX_183;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TileElementType::SmallScenery:
|
case TileElementType::SmallScenery:
|
||||||
case TileElementType::LargeScenery:
|
case TileElementType::LargeScenery:
|
||||||
colour = PALETTE_INDEX_99; // 64
|
paletteIndex = PALETTE_INDEX_99; // 64
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TileElementType::Entrance:
|
case TileElementType::Entrance:
|
||||||
colour = PALETTE_INDEX_186;
|
paletteIndex = PALETTE_INDEX_186;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -108,32 +115,58 @@ namespace OpenRCT2
|
|||||||
}
|
}
|
||||||
} while (!(tileElement++)->IsLastForTile());
|
} 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
|
// 0x0046DB4C
|
||||||
static std::optional<PreviewImage> generatePreviewMap()
|
static std::optional<PreviewImage> generatePreviewMap()
|
||||||
{
|
{
|
||||||
const auto& gameState = getGameState();
|
const auto& gameState = getGameState();
|
||||||
const auto previewSize = 128;
|
const auto drawableMapSize = TileCoordsXY{ gameState.mapSize.x - 2, gameState.mapSize.y - 2 };
|
||||||
const auto longEdgeSize = std::max(gameState.mapSize.x, gameState.mapSize.y);
|
const auto longEdgeSize = std::max(drawableMapSize.x, drawableMapSize.y);
|
||||||
const auto nearestPower = Numerics::ceil2(longEdgeSize, previewSize);
|
const auto idealPreviewSize = 150;
|
||||||
const auto mapSkipFactor = nearestPower / previewSize;
|
|
||||||
const auto offset = mapSkipFactor > 0 ? (nearestPower - longEdgeSize) / mapSkipFactor : 1;
|
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{
|
PreviewImage image{
|
||||||
.type = PreviewImageType::miniMap,
|
.type = PreviewImageType::miniMap,
|
||||||
.width = previewSize,
|
.width = previewWidth,
|
||||||
.height = previewSize,
|
.height = previewHeight,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto y = 0u; y < image.height; y++)
|
for (auto y = 0u; y < image.height; y++)
|
||||||
{
|
{
|
||||||
|
int32_t mapY = 1 + (y * mapSkipFactor);
|
||||||
|
if (mapY >= drawableMapSize.y)
|
||||||
|
break;
|
||||||
|
|
||||||
|
_tileColourIndex = y % 2;
|
||||||
|
|
||||||
for (auto x = 0u; x < image.width; x++)
|
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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user