mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-10 09:32:29 +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)
|
||||
------------------------------------------------------------------------
|
||||
- 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.
|
||||
|
||||
@@ -13,15 +13,20 @@
|
||||
#include <openrct2-ui/interface/Widget.h>
|
||||
#include <openrct2-ui/windows/Windows.h>
|
||||
#include <openrct2/Context.h>
|
||||
#include <openrct2/Diagnostic.h>
|
||||
#include <openrct2/FileClassifier.h>
|
||||
#include <openrct2/ParkImporter.h>
|
||||
#include <openrct2/SpriteIds.h>
|
||||
#include <openrct2/audio/Audio.h>
|
||||
#include <openrct2/config/Config.h>
|
||||
#include <openrct2/core/FileStream.h>
|
||||
#include <openrct2/core/String.hpp>
|
||||
#include <openrct2/drawing/Drawing.h>
|
||||
#include <openrct2/localisation/Formatter.h>
|
||||
#include <openrct2/localisation/Formatting.h>
|
||||
#include <openrct2/localisation/Localisation.Date.h>
|
||||
#include <openrct2/localisation/LocalisationService.h>
|
||||
#include <openrct2/park/ParkPreview.h>
|
||||
#include <openrct2/ride/RideData.h>
|
||||
#include <openrct2/scenario/Scenario.h>
|
||||
#include <openrct2/scenario/ScenarioRepository.h>
|
||||
@@ -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<void(std::string_view)> _callback;
|
||||
std::vector<ScenarioListItem> _listItems;
|
||||
const ScenarioIndexEntry* _highlightedScenario = nullptr;
|
||||
ParkPreview _preview;
|
||||
|
||||
public:
|
||||
ScenarioSelectWindow(std::function<void(std::string_view)> 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<const char*>(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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
colour = surfaceObject->MapColours[1];
|
||||
surfaceColour = paletteIndex = surfaceObject->MapColours[_tileColourIndex];
|
||||
}
|
||||
}
|
||||
|
||||
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,32 +115,58 @@ 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
|
||||
static std::optional<PreviewImage> 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;
|
||||
|
||||
_tileColourIndex = y % 2;
|
||||
|
||||
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