diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 12e272438e..1d40d847c7 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -3,6 +3,7 @@ - Feature: [#775] Add 2x and 4x zoom levels to software renderer (previously limited to OpenGL). - Feature: [#15642] Track design placement can now use construction modifier keys (ctrl/shift). - Feature: [#21521] [Plugin] Add hook 'park.guest.softcap.calculate' called before calculating the soft guest cap. +- Feature: [#22694] Park graphs have tooltips and can be resized like finance graphs. - Improved: [#22777] Add long flat-to-steep track pieces to the Wooden and Classic Wooden Roller Coasters. - Change: [#22596] Land ownership fixes described by .parkpatch files are now only considered on scenarios. - Change: [#22724] Staff now have optional ‘real’ names as well. diff --git a/src/openrct2-ui/interface/Graph.cpp b/src/openrct2-ui/interface/Graph.cpp index c1345969b7..be170829a3 100644 --- a/src/openrct2-ui/interface/Graph.cpp +++ b/src/openrct2-ui/interface/Graph.cpp @@ -161,45 +161,40 @@ namespace OpenRCT2::Graph } } - void DrawFinanceGraph(DrawPixelInfo& dpi, const GraphProperties& p) + template + static void DrawGraph( + DrawPixelInfo& dpi, const GraphProperties& p, const FmtString& labelFmt, const FmtString& tooltipFmt) { - const FmtString fmt("{BLACK}{CURRENCY2DP}"); - DrawYLabels(dpi, p.internalBounds, p.min, p.max, p.numYLabels, p.yLabelStepPx, p.lineCol, fmt); - DrawMonths(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx); - DrawLine(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max); - DrawLine(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max); + DrawYLabels(dpi, p.internalBounds, p.min, p.max, p.numYLabels, p.yLabelStepPx, p.lineCol, labelFmt); + DrawMonths(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx); + DrawLine(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max); + DrawLine(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max); if (p.hoverIdx >= 0 && p.hoverIdx < p.numPoints) { - const money64 value = p.series[p.hoverIdx]; - if (value != kMoney64Undefined) + const T value = p.series[p.hoverIdx]; + if (value != TkNoValue) { char buffer[64]{}; - FormatStringToBuffer(buffer, sizeof(buffer), "{CURRENCY2DP}", value); - DrawHoveredValue( + FormatStringToBuffer(buffer, sizeof(buffer), tooltipFmt, value); + DrawHoveredValue( dpi, value, p.hoverIdx, p.internalBounds, p.xStepPx, p.min, p.max, buffer, p.lineCol.withFlag(ColourFlag::withOutline, true)); } } } - void DrawRatingGraph(DrawPixelInfo& dpi, const GraphProperties& p) + void DrawFinanceGraph(DrawPixelInfo& dpi, const GraphProperties& p) { - constexpr uint8_t noValue = kParkRatingHistoryUndefined; - const FmtString fmt("{BLACK}{COMMA32}"); - // Since the park rating rating history is divided by 4, we have to fudge the max number here. - DrawYLabels(dpi, p.internalBounds, p.min, kParkRatingMax, p.numYLabels, p.yLabelStepPx, p.lineCol, fmt); - DrawMonths(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx); - DrawLine(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max); - DrawLine(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max); + DrawGraph(dpi, p, "{BLACK}{CURRENCY2DP}", "{CURRENCY2DP}"); + } + + void DrawRatingGraph(DrawPixelInfo& dpi, const GraphProperties& p) + { + DrawGraph(dpi, p, "{BLACK}{COMMA32}", "{COMMA32}"); } void DrawGuestGraph(DrawPixelInfo& dpi, const GraphProperties& p) { - constexpr uint32_t noValue = kGuestsInParkHistoryUndefined; - const FmtString fmt("{BLACK}{COMMA32}"); - DrawYLabels(dpi, p.internalBounds, p.min, p.max, p.numYLabels, p.yLabelStepPx, p.lineCol, fmt); - DrawMonths(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx); - DrawLine(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max); - DrawLine(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max); + DrawGraph(dpi, p, "{BLACK}{COMMA32}", "{COMMA32}"); } } // namespace OpenRCT2::Graph diff --git a/src/openrct2-ui/interface/Graph.h b/src/openrct2-ui/interface/Graph.h index 6cba29fbbd..ff6398b751 100644 --- a/src/openrct2-ui/interface/Graph.h +++ b/src/openrct2-ui/interface/Graph.h @@ -17,7 +17,6 @@ namespace OpenRCT2::Graph { constexpr int32_t kYTickMarkPadding = 8; - constexpr int32_t kParkRatingMax = 1000; template struct GraphProperties { @@ -68,6 +67,6 @@ namespace OpenRCT2::Graph }; void DrawFinanceGraph(DrawPixelInfo& dpi, const GraphProperties& p); - void DrawRatingGraph(DrawPixelInfo& dpi, const GraphProperties& p); + void DrawRatingGraph(DrawPixelInfo& dpi, const GraphProperties& p); void DrawGuestGraph(DrawPixelInfo& dpi, const GraphProperties& p); } // namespace OpenRCT2::Graph diff --git a/src/openrct2-ui/windows/Finances.cpp b/src/openrct2-ui/windows/Finances.cpp index 2aaa3b0e89..a152c51583 100644 --- a/src/openrct2-ui/windows/Finances.cpp +++ b/src/openrct2-ui/windows/Finances.cpp @@ -832,7 +832,7 @@ namespace OpenRCT2::Ui::Windows _graphProps.min = centredGraph ? -max : 0.00_GBP; _graphProps.max = max; - // dynamic padding for long axis lables: + // dynamic padding for long axis labels: char buffer[64]{}; FormatStringToBuffer(buffer, sizeof(buffer), "{BLACK}{CURRENCY2DP}", centredGraph ? -max : max); int32_t maxWidth = GfxGetStringWidth(buffer, FontStyle::Small) + Graph::kYTickMarkPadding + 1; diff --git a/src/openrct2-ui/windows/Park.cpp b/src/openrct2-ui/windows/Park.cpp index 45cfebb08f..cc951ae8a6 100644 --- a/src/openrct2-ui/windows/Park.cpp +++ b/src/openrct2-ui/windows/Park.cpp @@ -39,8 +39,8 @@ namespace OpenRCT2::Ui::Windows static constexpr StringId WINDOW_TITLE = STR_STRINGID; static constexpr int32_t WH = 224; - static constexpr ScreenCoordsXY kGraphTopLeftPadding{ 45, 15 }; - static constexpr ScreenCoordsXY kGraphBottomRightPadding{ 5, 5 }; + static constexpr ScreenCoordsXY kGraphTopLeftPadding{ 45, 20 }; + static constexpr ScreenCoordsXY kGraphBottomRightPadding{ 25, 10 }; static constexpr uint8_t kGraphNumYLabels = 6; enum WindowParkPage @@ -203,7 +203,7 @@ namespace OpenRCT2::Ui::Windows int32_t _numberOfRides = -1; uint8_t _peepAnimationFrame = 0; - Graph::GraphProperties _ratingProps{}; + Graph::GraphProperties _ratingProps{}; Graph::GraphProperties _guestProps{}; ScreenRect _ratingGraphBounds; @@ -685,13 +685,18 @@ namespace OpenRCT2::Ui::Windows #pragma region Rating page void OnResizeRating() { - WindowSetResize(*this, 255, 182, 255, 182); + flags |= WF_RESIZABLE; + WindowSetResize(*this, 268, 174 + 9, 2000, 2000); } void OnUpdateRating() { frame_no++; WidgetInvalidate(*this, WIDX_TAB_2); + if (_ratingProps.UpdateHoverIndex()) + { + InvalidateWidget(WIDX_BACKGROUND); + } } void OnPrepareDrawRating() @@ -710,14 +715,14 @@ namespace OpenRCT2::Ui::Windows AnchorBorderWidgets(); _ratingProps.min = 0; - _ratingProps.max = 250; + _ratingProps.max = 1000; _ratingProps.series = GetGameState().Park.RatingHistory; const Widget* background = &widgets[WIDX_PAGE_BACKGROUND]; _ratingGraphBounds = { windowPos + ScreenCoordsXY{ background->left + 4, background->top + 15 }, windowPos + ScreenCoordsXY{ background->right - 4, background->bottom - 4 } }; char buffer[64]{}; - FormatStringToBuffer(buffer, sizeof(buffer), "{BLACK}{COMMA32}", Graph::kParkRatingMax); + FormatStringToBuffer(buffer, sizeof(buffer), "{BLACK}{COMMA32}", _ratingProps.max); int32_t maxWidth = GfxGetStringWidth(buffer, FontStyle::Small) + Graph::kYTickMarkPadding + 1; const ScreenCoordsXY dynamicPadding{ std::max(maxWidth, kGraphTopLeftPadding.x), kGraphTopLeftPadding.y }; @@ -740,6 +745,13 @@ namespace OpenRCT2::Ui::Windows // Graph border GfxFillRectInset(dpi, _ratingGraphBounds, colours[1], INSET_RECT_F_30); + // hide resize widget on graph area + constexpr ScreenCoordsXY offset{ 1, 1 }; + constexpr ScreenCoordsXY bigOffset{ 5, 5 }; + GfxFillRectInset( + dpi, { _ratingGraphBounds.Point2 - bigOffset, _ratingGraphBounds.Point2 - offset }, colours[1], + INSET_RECT_FLAG_FILL_DONT_LIGHTEN | INSET_RECT_FLAG_BORDER_NONE); + Graph::DrawRatingGraph(dpi, _ratingProps); } @@ -748,7 +760,8 @@ namespace OpenRCT2::Ui::Windows #pragma region Guests page void OnResizeGuests() { - WindowSetResize(*this, 255, 182, 255, 182); + flags |= WF_RESIZABLE; + WindowSetResize(*this, 268, 174 + 9, 2000, 2000); } void OnUpdateGuests() @@ -756,6 +769,10 @@ namespace OpenRCT2::Ui::Windows frame_no++; _peepAnimationFrame = (_peepAnimationFrame + 1) % 24; WidgetInvalidate(*this, WIDX_TAB_3); + if (_guestProps.UpdateHoverIndex()) + { + InvalidateWidget(WIDX_BACKGROUND); + } } void OnPrepareDrawGuests() @@ -815,6 +832,13 @@ namespace OpenRCT2::Ui::Windows // Graph border GfxFillRectInset(dpi, _guestGraphBounds, colours[1], INSET_RECT_F_30); + // hide resize widget on graph area + constexpr ScreenCoordsXY offset{ 1, 1 }; + constexpr ScreenCoordsXY bigOffset{ 5, 5 }; + GfxFillRectInset( + dpi, { _guestGraphBounds.Point2 - bigOffset, _guestGraphBounds.Point2 - offset }, colours[1], + INSET_RECT_FLAG_FILL_DONT_LIGHTEN | INSET_RECT_FLAG_BORDER_NONE); + Graph::DrawGuestGraph(dpi, _guestProps); } diff --git a/src/openrct2/park/ParkFile.cpp b/src/openrct2/park/ParkFile.cpp index 13b8bdeaa4..f1564f05dc 100644 --- a/src/openrct2/park/ParkFile.cpp +++ b/src/openrct2/park/ParkFile.cpp @@ -910,10 +910,52 @@ namespace OpenRCT2 return true; }); - cs.ReadWriteArray(gameState.Park.RatingHistory, [&cs](uint8_t& value) { - cs.ReadWrite(value); - return true; - }); + if (version < k16BitParkHistoryVersion) + { + if (cs.GetMode() == OrcaStream::Mode::READING) + { + uint8_t smallHistory[kParkRatingHistorySize]; + cs.ReadWriteArray(smallHistory, [&cs](uint8_t& value) { + cs.ReadWrite(value); + return true; + }); + for (int i = 0; i < kParkRatingHistorySize; i++) + { + if (smallHistory[i] == RCT12ParkHistoryUndefined) + gameState.Park.RatingHistory[i] = kParkRatingHistoryUndefined; + else + { + gameState.Park.RatingHistory[i] = static_cast( + smallHistory[i] * RCT12ParkRatingHistoryFactor); + } + } + } + else + { + uint8_t smallHistory[kParkRatingHistorySize]; + for (int i = 0; i < kParkRatingHistorySize; i++) + { + if (gameState.Park.RatingHistory[i] == kParkRatingHistoryUndefined) + smallHistory[i] = RCT12ParkHistoryUndefined; + else + { + smallHistory[i] = static_cast( + gameState.Park.RatingHistory[i] / RCT12ParkRatingHistoryFactor); + } + } + cs.ReadWriteArray(smallHistory, [&cs](uint8_t& value) { + cs.ReadWrite(value); + return true; + }); + } + } + else + { + cs.ReadWriteArray(gameState.Park.RatingHistory, [&cs](uint16_t& value) { + cs.ReadWrite(value); + return true; + }); + } cs.ReadWriteArray(gameState.GuestsInParkHistory, [&cs](uint32_t& value) { cs.ReadWrite(value); diff --git a/src/openrct2/park/ParkFile.h b/src/openrct2/park/ParkFile.h index 30e005c9b2..602e508085 100644 --- a/src/openrct2/park/ParkFile.h +++ b/src/openrct2/park/ParkFile.h @@ -11,10 +11,10 @@ namespace OpenRCT2 struct GameState_t; // Current version that is saved. - constexpr uint32_t PARK_FILE_CURRENT_VERSION = 37; + constexpr uint32_t PARK_FILE_CURRENT_VERSION = 38; // The minimum version that is forwards compatible with the current version. - constexpr uint32_t PARK_FILE_MIN_VERSION = 33; + constexpr uint32_t PARK_FILE_MIN_VERSION = 38; // The minimum version that is backwards compatible with the current version. // If this is increased beyond 0, uncomment the checks in ParkFile.cpp and Context.cpp! @@ -29,6 +29,7 @@ namespace OpenRCT2 constexpr uint16_t kBlockBrakeImprovementsVersion = 27; constexpr uint16_t kGigaCoasterInversions = 31; constexpr uint16_t kWoodenFlatToSteepVersion = 37; + constexpr uint16_t k16BitParkHistoryVersion = 38; } // namespace OpenRCT2 class ParkFileExporter diff --git a/src/openrct2/rct1/S4Importer.cpp b/src/openrct2/rct1/S4Importer.cpp index 5bf44746fb..7bd9df3621 100644 --- a/src/openrct2/rct1/S4Importer.cpp +++ b/src/openrct2/rct1/S4Importer.cpp @@ -2150,7 +2150,13 @@ namespace OpenRCT2::RCT1 gameState.Park.Rating = _s4.ParkRating; Park::ResetHistories(gameState); - std::copy(std::begin(_s4.ParkRatingHistory), std::end(_s4.ParkRatingHistory), gameState.Park.RatingHistory); + for (size_t i = 0; i < std::size(_s4.ParkRatingHistory); i++) + { + if (_s4.ParkRatingHistory[i] != RCT12ParkHistoryUndefined) + { + gameState.Park.RatingHistory[i] = _s4.ParkRatingHistory[i] * RCT12ParkRatingHistoryFactor; + } + } for (size_t i = 0; i < std::size(_s4.GuestsInParkHistory); i++) { if (_s4.GuestsInParkHistory[i] != RCT12ParkHistoryUndefined) diff --git a/src/openrct2/rct12/RCT12.h b/src/openrct2/rct12/RCT12.h index 7b37c420b0..39d55ad95e 100644 --- a/src/openrct2/rct12/RCT12.h +++ b/src/openrct2/rct12/RCT12.h @@ -71,6 +71,7 @@ constexpr uint16_t RCT12VehicleTrackTypeMask = 0b1111111111111100; constexpr uint8_t RCT12PeepThoughtItemNone = std::numeric_limits::max(); constexpr uint8_t RCT12GuestsInParkHistoryFactor = 20; +constexpr uint8_t RCT12ParkRatingHistoryFactor = 4; constexpr uint8_t RCT12ParkHistoryUndefined = std::numeric_limits::max(); constexpr uint8_t kTD46RatingsMultiplier = 10; diff --git a/src/openrct2/rct2/S6Importer.cpp b/src/openrct2/rct2/S6Importer.cpp index 91abf668ec..085a87652f 100644 --- a/src/openrct2/rct2/S6Importer.cpp +++ b/src/openrct2/rct2/S6Importer.cpp @@ -380,7 +380,13 @@ namespace OpenRCT2::RCT2 gameState.Park.Rating = _s6.ParkRating; Park::ResetHistories(gameState); - std::copy(std::begin(_s6.ParkRatingHistory), std::end(_s6.ParkRatingHistory), gameState.Park.RatingHistory); + for (size_t i = 0; i < std::size(_s6.ParkRatingHistory); i++) + { + if (_s6.ParkRatingHistory[i] != RCT12ParkHistoryUndefined) + { + gameState.Park.RatingHistory[i] = _s6.ParkRatingHistory[i] * RCT12ParkRatingHistoryFactor; + } + } for (size_t i = 0; i < std::size(_s6.GuestsInParkHistory); i++) { if (_s6.GuestsInParkHistory[i] != RCT12ParkHistoryUndefined) diff --git a/src/openrct2/world/Park.cpp b/src/openrct2/world/Park.cpp index 3f701ac96a..cc843e0a5d 100644 --- a/src/openrct2/world/Park.cpp +++ b/src/openrct2/world/Park.cpp @@ -605,7 +605,7 @@ namespace OpenRCT2::Park // Update park rating, guests in park and current cash history constexpr auto ratingHistorySize = std::extent_v; - HistoryPushRecord(gameState.Park.RatingHistory, gameState.Park.Rating / 4); + HistoryPushRecord(gameState.Park.RatingHistory, gameState.Park.Rating); constexpr auto numGuestsHistorySize = std::extent_v; HistoryPushRecord(gameState.GuestsInParkHistory, gameState.NumGuestsInPark); diff --git a/src/openrct2/world/Park.h b/src/openrct2/world/Park.h index 26a87ee1a5..023c7e9fb9 100644 --- a/src/openrct2/world/Park.h +++ b/src/openrct2/world/Park.h @@ -14,7 +14,7 @@ constexpr auto MAX_ENTRANCE_FEE = 999.00_GBP; -constexpr uint8_t kParkRatingHistoryUndefined = std::numeric_limits::max(); +constexpr uint16_t kParkRatingHistoryUndefined = std::numeric_limits::max(); constexpr uint32_t kGuestsInParkHistoryUndefined = std::numeric_limits::max(); constexpr uint8_t kParkRatingHistorySize = 32; constexpr uint8_t kGuestsInParkHistorySize = 32; @@ -61,7 +61,7 @@ namespace OpenRCT2 std::string Name; uint64_t Flags; uint16_t Rating; - uint8_t RatingHistory[kParkRatingHistorySize]; + uint16_t RatingHistory[kParkRatingHistorySize]; int16_t RatingCasualtyPenalty; money64 EntranceFee; std::vector Entrances;