mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-22 14:24:33 +01:00
Merge pull request #22414 from mrmbernardi/fix-graphs
Improve financial and park history graphs
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
0.4.14 (in development)
|
||||
------------------------------------------------------------------------
|
||||
- Feature: [#15750] Allow using different types of park entrance in one park.
|
||||
- Feature: [#22414] Finance graphs can be resized.
|
||||
- Change: [#21659] Increase the Hybrid Roller Coaster’s maximum lift speed to 17 km/h (11 mph).
|
||||
- Change: [#22466] The Clear Scenery tool now uses a bulldozer cursor instead of a generic crosshair.
|
||||
- Fix: [#22307] Hover tooltips in financial charts are not invalidated properly.
|
||||
- Fix: [#22395, #22396] Misaligned tick marks in financial and guest count graphs (original bug).
|
||||
|
||||
0.4.13 (2024-08-04)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
@@ -10,282 +10,189 @@
|
||||
#include "../UiStringIds.h"
|
||||
|
||||
#include <openrct2-ui/interface/Graph.h>
|
||||
#include <openrct2/Context.h>
|
||||
#include <openrct2/Date.h>
|
||||
#include <openrct2/localisation/Formatter.h>
|
||||
#include <openrct2/localisation/Formatting.h>
|
||||
#include <openrct2/localisation/Localisation.Date.h>
|
||||
|
||||
using namespace OpenRCT2;
|
||||
#include <openrct2/world/Park.h>
|
||||
|
||||
namespace OpenRCT2::Graph
|
||||
{
|
||||
static void DrawMonths(DrawPixelInfo& dpi, const uint8_t* history, int32_t count, const ScreenCoordsXY& origCoords)
|
||||
constexpr int32_t kDashLength = 2;
|
||||
|
||||
template<typename T>
|
||||
static void DrawYLabels(
|
||||
DrawPixelInfo& dpi, const ScreenRect& internalBounds, const T min, const T max, const int32_t numYLabels,
|
||||
const int32_t yLabelStepPx, const ColourWithFlags lineCol, const FmtString& fmt)
|
||||
{
|
||||
T curLabel = max;
|
||||
int32_t curScreenPos = internalBounds.GetTop() - 5;
|
||||
const T yLabelStep = (max - min) / (numYLabels - 1);
|
||||
for (int32_t i = 0; i < numYLabels; i++)
|
||||
{
|
||||
char buffer[64]{};
|
||||
FormatStringToBuffer(buffer, sizeof(buffer), fmt, curLabel);
|
||||
DrawText(dpi, { internalBounds.GetLeft(), curScreenPos }, { FontStyle::Small, TextAlignment::RIGHT }, buffer);
|
||||
GfxFillRectInset(
|
||||
dpi, { { internalBounds.GetLeft(), curScreenPos + 5 }, { internalBounds.GetRight(), curScreenPos + 5 } },
|
||||
lineCol, INSET_RECT_FLAG_BORDER_INSET);
|
||||
curScreenPos += yLabelStepPx;
|
||||
curLabel -= yLabelStep;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, T TkNoValue>
|
||||
static void DrawMonths(DrawPixelInfo& dpi, const T* series, int32_t count, const ScreenRect& bounds, const int32_t xStep)
|
||||
{
|
||||
auto& date = GetDate();
|
||||
int32_t currentMonth = date.GetMonth();
|
||||
int32_t currentDay = date.GetMonthTicks();
|
||||
int32_t yearOver32 = (currentMonth * 4) + (currentDay >> 14) - 31;
|
||||
auto screenCoords = origCoords;
|
||||
auto screenCoords = bounds.Point1;
|
||||
for (int32_t i = count - 1; i >= 0; i--)
|
||||
{
|
||||
if (history[i] != 255 && yearOver32 % 4 == 0)
|
||||
{
|
||||
// Draw month text
|
||||
auto ft = Formatter();
|
||||
ft.Add<uint32_t>(DateGameShortMonthNames[DateGetMonth((yearOver32 / 4) + MONTH_COUNT)]);
|
||||
DrawTextBasic(
|
||||
dpi, screenCoords - ScreenCoordsXY{ 0, 10 }, STR_GRAPH_LABEL, ft,
|
||||
{ FontStyle::Small, TextAlignment::CENTRE });
|
||||
|
||||
// Draw month mark
|
||||
GfxFillRect(dpi, { screenCoords, screenCoords + ScreenCoordsXY{ 0, 3 } }, PALETTE_INDEX_10);
|
||||
}
|
||||
|
||||
yearOver32 = (yearOver32 + 1) % 32;
|
||||
screenCoords.x += 6;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawLineA(DrawPixelInfo& dpi, const uint8_t* history, int32_t count, const ScreenCoordsXY& origCoords)
|
||||
{
|
||||
auto lastCoords = ScreenCoordsXY{ -1, -1 };
|
||||
auto coords = origCoords;
|
||||
for (int32_t i = count - 1; i >= 0; i--)
|
||||
{
|
||||
if (history[i] != 255)
|
||||
{
|
||||
coords.y = origCoords.y + ((255 - history[i]) * 100) / 256;
|
||||
|
||||
if (lastCoords.x != -1)
|
||||
{
|
||||
auto leftTop1 = lastCoords + ScreenCoordsXY{ 1, 1 };
|
||||
auto rightBottom1 = coords + ScreenCoordsXY{ 1, 1 };
|
||||
auto leftTop2 = lastCoords + ScreenCoordsXY{ 0, 1 };
|
||||
auto rightBottom2 = coords + ScreenCoordsXY{ 0, 1 };
|
||||
GfxDrawLine(dpi, { leftTop1, rightBottom1 }, PALETTE_INDEX_10);
|
||||
GfxDrawLine(dpi, { leftTop2, rightBottom2 }, PALETTE_INDEX_10);
|
||||
}
|
||||
if (i == 0)
|
||||
GfxFillRect(dpi, { coords, coords + ScreenCoordsXY{ 2, 2 } }, PALETTE_INDEX_10);
|
||||
|
||||
lastCoords = coords;
|
||||
}
|
||||
coords.x += 6;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawLineB(DrawPixelInfo& dpi, const uint8_t* history, int32_t count, const ScreenCoordsXY& origCoords)
|
||||
{
|
||||
auto lastCoords = ScreenCoordsXY{ -1, -1 };
|
||||
auto coords = origCoords;
|
||||
for (int32_t i = count - 1; i >= 0; i--)
|
||||
{
|
||||
if (history[i] != 255)
|
||||
{
|
||||
coords.y = origCoords.y + ((255 - history[i]) * 100) / 256;
|
||||
|
||||
if (lastCoords.x != -1)
|
||||
{
|
||||
auto leftTop = lastCoords;
|
||||
auto rightBottom = coords;
|
||||
GfxDrawLine(dpi, { leftTop, rightBottom }, PALETTE_INDEX_21);
|
||||
}
|
||||
if (i == 0)
|
||||
GfxFillRect(dpi, { coords - ScreenCoordsXY{ 1, 1 }, coords + ScreenCoordsXY{ 1, 1 } }, PALETTE_INDEX_21);
|
||||
|
||||
lastCoords = coords;
|
||||
}
|
||||
coords.x += 6;
|
||||
}
|
||||
}
|
||||
|
||||
void Draw(DrawPixelInfo& dpi, uint8_t* history, int32_t count, const ScreenCoordsXY& screenPos)
|
||||
{
|
||||
DrawMonths(dpi, history, count, screenPos);
|
||||
DrawLineA(dpi, history, count, screenPos);
|
||||
DrawLineB(dpi, history, count, screenPos);
|
||||
}
|
||||
} // namespace OpenRCT2::Graph
|
||||
|
||||
struct FinancialTooltipInfo
|
||||
{
|
||||
const ScreenCoordsXY coords;
|
||||
const money64 money{};
|
||||
};
|
||||
|
||||
static constexpr auto ChartMaxDataCount = 64;
|
||||
static constexpr auto ChartMaxIndex = ChartMaxDataCount - 1;
|
||||
static constexpr auto ChartDataWidth = 6;
|
||||
static constexpr auto ChartMaxWidth = ChartMaxIndex * ChartDataWidth;
|
||||
static constexpr auto ChartMaxHeight = 164;
|
||||
static constexpr auto CursorXOffset = 3;
|
||||
static constexpr auto DefaultDashedLength = 2;
|
||||
|
||||
static int32_t IndexForCursorAndHistory(const int32_t historyCount, const int32_t cursorX, const int32_t chartX)
|
||||
{
|
||||
const auto offsettedCursorX = cursorX + CursorXOffset;
|
||||
return (historyCount - 1) - (offsettedCursorX - chartX) / ChartDataWidth;
|
||||
}
|
||||
|
||||
static const ScreenCoordsXY ScreenCoordsForHistoryIndex(
|
||||
const int32_t index, const money64* history, const int32_t chartX, const int32_t chartY, const int32_t modifier,
|
||||
const int32_t offset)
|
||||
{
|
||||
auto coords = ScreenCoordsXY{ chartX + ChartDataWidth * (ChartMaxIndex - index),
|
||||
chartY + ChartMaxHeight
|
||||
- (((static_cast<int32_t>(history[index] >> modifier) + offset) * 170) / 256) };
|
||||
return coords;
|
||||
}
|
||||
|
||||
static const FinancialTooltipInfo FinanceTooltipInfoFromMoney(
|
||||
const money64* history, const int32_t historyCount, const int32_t modifier, const int32_t offset,
|
||||
const ScreenRect& chartFrame, const ScreenCoordsXY& cursorPosition)
|
||||
{
|
||||
if (!chartFrame.Contains(cursorPosition))
|
||||
{
|
||||
return { {}, kMoney64Undefined };
|
||||
}
|
||||
|
||||
const auto historyIndex = IndexForCursorAndHistory(historyCount, cursorPosition.x, chartFrame.GetLeft());
|
||||
const auto coords = ScreenCoordsForHistoryIndex(
|
||||
historyIndex, history, chartFrame.GetLeft(), chartFrame.GetTop(), modifier, offset);
|
||||
|
||||
return { coords, history[historyIndex] };
|
||||
}
|
||||
|
||||
namespace OpenRCT2::Graph
|
||||
{
|
||||
static void DrawMonths(DrawPixelInfo& dpi, const money64* history, int32_t count, const ScreenCoordsXY& origCoords)
|
||||
{
|
||||
auto& date = GetDate();
|
||||
int32_t currentMonth = date.GetMonth();
|
||||
int32_t currentDay = date.GetMonthTicks();
|
||||
int32_t yearOver32 = (currentMonth * 4) + (currentDay >> 14) - 31;
|
||||
auto screenCoords = origCoords;
|
||||
for (int32_t i = count - 1; i >= 0; i--)
|
||||
{
|
||||
if (history[i] != kMoney64Undefined && yearOver32 % 4 == 0)
|
||||
if (series[i] != TkNoValue && yearOver32 % 4 == 0)
|
||||
{
|
||||
// Draw month text
|
||||
auto ft = Formatter();
|
||||
ft.Add<StringId>(DateGameShortMonthNames[DateGetMonth((yearOver32 / 4) + MONTH_COUNT)]);
|
||||
DrawTextBasic(
|
||||
dpi, screenCoords - ScreenCoordsXY{ 0, 10 }, STR_GRAPH_LABEL, ft,
|
||||
dpi, screenCoords - ScreenCoordsXY{ 0, 14 }, STR_GRAPH_LABEL, ft,
|
||||
{ FontStyle::Small, TextAlignment::CENTRE });
|
||||
|
||||
// Draw month mark
|
||||
GfxFillRect(dpi, { screenCoords, screenCoords + ScreenCoordsXY{ 0, 3 } }, PALETTE_INDEX_10);
|
||||
GfxFillRect(
|
||||
dpi, { screenCoords - ScreenCoordsXY{ 0, 4 }, screenCoords - ScreenCoordsXY{ 0, 1 } }, PALETTE_INDEX_10);
|
||||
}
|
||||
|
||||
yearOver32 = (yearOver32 + 1) % 32;
|
||||
screenCoords.x += 6;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawLineA(
|
||||
DrawPixelInfo& dpi, const money64* history, int32_t count, const ScreenCoordsXY& origCoords, int32_t modifier,
|
||||
int32_t offset)
|
||||
{
|
||||
ScreenCoordsXY lastCoords;
|
||||
bool lastCoordsValid = false;
|
||||
auto coords = origCoords;
|
||||
for (int32_t i = count - 1; i >= 0; i--)
|
||||
{
|
||||
if (history[i] != kMoney64Undefined)
|
||||
{
|
||||
coords.y = origCoords.y + 170 - 6 - ((((history[i] >> modifier) + offset) * 170) / 256);
|
||||
|
||||
if (lastCoordsValid)
|
||||
{
|
||||
auto leftTop1 = lastCoords + ScreenCoordsXY{ 1, 1 };
|
||||
auto rightBottom1 = coords + ScreenCoordsXY{ 1, 1 };
|
||||
auto leftTop2 = lastCoords + ScreenCoordsXY{ 0, 1 };
|
||||
auto rightBottom2 = coords + ScreenCoordsXY{ 0, 1 };
|
||||
GfxDrawLine(dpi, { leftTop1, rightBottom1 }, PALETTE_INDEX_10);
|
||||
GfxDrawLine(dpi, { leftTop2, rightBottom2 }, PALETTE_INDEX_10);
|
||||
}
|
||||
if (i == 0)
|
||||
GfxFillRect(dpi, { coords, coords + ScreenCoordsXY{ 2, 2 } }, PALETTE_INDEX_10);
|
||||
|
||||
lastCoords = coords;
|
||||
lastCoordsValid = true;
|
||||
}
|
||||
coords.x += 6;
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawLineB(
|
||||
DrawPixelInfo& dpi, const money64* history, int32_t count, const ScreenCoordsXY& origCoords, int32_t modifier,
|
||||
int32_t offset)
|
||||
{
|
||||
ScreenCoordsXY lastCoords;
|
||||
bool lastCoordsValid = false;
|
||||
auto coords = origCoords;
|
||||
for (int32_t i = count - 1; i >= 0; i--)
|
||||
{
|
||||
if (history[i] != kMoney64Undefined)
|
||||
{
|
||||
coords.y = origCoords.y + 170 - 6 - ((((history[i] >> modifier) + offset) * 170) / 256);
|
||||
|
||||
if (lastCoordsValid)
|
||||
{
|
||||
auto leftTop = lastCoords;
|
||||
auto rightBottom = coords;
|
||||
GfxDrawLine(dpi, { leftTop, rightBottom }, PALETTE_INDEX_21);
|
||||
}
|
||||
if (i == 0)
|
||||
GfxFillRect(dpi, { coords - ScreenCoordsXY{ 1, 1 }, coords + ScreenCoordsXY{ 1, 1 } }, PALETTE_INDEX_21);
|
||||
|
||||
lastCoords = coords;
|
||||
lastCoordsValid = true;
|
||||
}
|
||||
coords.x += 6;
|
||||
screenCoords.x += xStep;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static void DrawHoveredValue(
|
||||
DrawPixelInfo& dpi, const money64* history, const int32_t historyCount, const ScreenCoordsXY& screenCoords,
|
||||
const int32_t modifier, const int32_t offset)
|
||||
DrawPixelInfo& dpi, const T value, const int32_t hoverIdx, const ScreenRect& bounds, const int32_t xStep,
|
||||
const T minValue, const T maxValue, const_utf8string text, ColourWithFlags textCol)
|
||||
{
|
||||
const auto cursorPosition = ContextGetCursorPositionScaled();
|
||||
const ScreenRect chartFrame{ screenCoords, screenCoords + ScreenCoordsXY{ ChartMaxWidth, ChartMaxHeight } };
|
||||
const int32_t screenRange = bounds.GetHeight();
|
||||
const int32_t valueRange = maxValue - minValue;
|
||||
|
||||
if (!chartFrame.Contains(cursorPosition))
|
||||
{
|
||||
return;
|
||||
}
|
||||
const int32_t yPosition = bounds.GetBottom() - ((value - minValue) * screenRange) / valueRange;
|
||||
ScreenCoordsXY coords = { bounds.GetRight() - hoverIdx * xStep, yPosition };
|
||||
|
||||
const auto info = FinanceTooltipInfoFromMoney(history, ChartMaxDataCount, modifier, offset, chartFrame, cursorPosition);
|
||||
GfxDrawDashedLine(
|
||||
dpi,
|
||||
{
|
||||
{ coords.x, bounds.GetTop() },
|
||||
{ coords.x, bounds.GetBottom() },
|
||||
},
|
||||
kDashLength, PALETTE_INDEX_10);
|
||||
GfxDrawDashedLine(dpi, { { bounds.GetLeft(), coords.y }, coords }, kDashLength, PALETTE_INDEX_10);
|
||||
|
||||
if (info.money == kMoney64Undefined)
|
||||
{
|
||||
return;
|
||||
}
|
||||
GfxDrawDashedLine(dpi, { { info.coords.x, chartFrame.GetTop() }, info.coords }, DefaultDashedLength, 0);
|
||||
GfxDrawDashedLine(dpi, { { chartFrame.GetLeft() - 10, info.coords.y }, info.coords }, DefaultDashedLength, 0);
|
||||
Formatter ft;
|
||||
ft.Add<money64>(value);
|
||||
DrawText(dpi, coords - ScreenCoordsXY{ 0, 16 }, { textCol, TextAlignment::CENTRE }, text);
|
||||
|
||||
if (cursorPosition.y > info.coords.y)
|
||||
{
|
||||
GfxDrawDashedLine(dpi, { info.coords, { info.coords.x, cursorPosition.y } }, DefaultDashedLength, 0);
|
||||
}
|
||||
|
||||
auto ft = Formatter();
|
||||
ft.Add<money64>(info.money);
|
||||
DrawTextBasic(
|
||||
dpi, info.coords - ScreenCoordsXY{ 0, 16 }, STR_FINANCES_SUMMARY_EXPENDITURE_VALUE, ft, { TextAlignment::CENTRE });
|
||||
|
||||
GfxFillRect(dpi, { { info.coords - ScreenCoordsXY{ 2, 2 } }, info.coords + ScreenCoordsXY{ 2, 2 } }, PALETTE_INDEX_10);
|
||||
GfxFillRect(
|
||||
dpi, { { info.coords - ScreenCoordsXY{ 1, 1 } }, { info.coords + ScreenCoordsXY{ 1, 1 } } }, PALETTE_INDEX_21);
|
||||
GfxFillRect(dpi, { { coords - ScreenCoordsXY{ 2, 2 } }, coords + ScreenCoordsXY{ 2, 2 } }, PALETTE_INDEX_10);
|
||||
GfxFillRect(dpi, { { coords - ScreenCoordsXY{ 1, 1 } }, { coords + ScreenCoordsXY{ 1, 1 } } }, PALETTE_INDEX_21);
|
||||
}
|
||||
|
||||
void Draw(
|
||||
DrawPixelInfo& dpi, const money64* history, const int32_t count, const ScreenCoordsXY& screenCoords,
|
||||
const int32_t modifier, const int32_t offset)
|
||||
template<typename T, T TkNoValue, bool TbackgroundLine>
|
||||
static void DrawLine(
|
||||
DrawPixelInfo& dpi, const T* series, const int32_t count, const ScreenRect& bounds, const int32_t xStep,
|
||||
const T minValue, const T maxValue)
|
||||
{
|
||||
DrawMonths(dpi, history, count, screenCoords);
|
||||
DrawLineA(dpi, history, count, screenCoords, modifier, offset);
|
||||
DrawLineB(dpi, history, count, screenCoords, modifier, offset);
|
||||
DrawHoveredValue(dpi, history, count, screenCoords, modifier, offset);
|
||||
const int32_t screenRange = bounds.GetHeight();
|
||||
const int32_t valueRange = maxValue - minValue;
|
||||
|
||||
ScreenCoordsXY lastCoords;
|
||||
bool lastCoordsValid = false;
|
||||
ScreenCoordsXY coords = bounds.Point1;
|
||||
for (int32_t i = count - 1; i >= 0; i--)
|
||||
{
|
||||
auto value = series[i];
|
||||
if (value != TkNoValue)
|
||||
{
|
||||
coords.y = bounds.GetBottom() - ((value - minValue) * screenRange) / valueRange;
|
||||
|
||||
if constexpr (TbackgroundLine)
|
||||
{
|
||||
if (lastCoordsValid)
|
||||
{
|
||||
auto leftTop1 = lastCoords + ScreenCoordsXY{ 1, 1 };
|
||||
auto rightBottom1 = coords + ScreenCoordsXY{ 1, 1 };
|
||||
auto leftTop2 = lastCoords + ScreenCoordsXY{ 0, 1 };
|
||||
auto rightBottom2 = coords + ScreenCoordsXY{ 0, 1 };
|
||||
GfxDrawLine(dpi, { leftTop1, rightBottom1 }, PALETTE_INDEX_10);
|
||||
GfxDrawLine(dpi, { leftTop2, rightBottom2 }, PALETTE_INDEX_10);
|
||||
}
|
||||
if (i == 0)
|
||||
{
|
||||
GfxFillRect(dpi, { coords, coords + ScreenCoordsXY{ 2, 2 } }, PALETTE_INDEX_10);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastCoordsValid)
|
||||
{
|
||||
auto leftTop = lastCoords;
|
||||
auto rightBottom = coords;
|
||||
GfxDrawLine(dpi, { leftTop, rightBottom }, PALETTE_INDEX_21);
|
||||
}
|
||||
if (i == 0)
|
||||
{
|
||||
GfxFillRect(
|
||||
dpi, { coords - ScreenCoordsXY{ 1, 1 }, coords + ScreenCoordsXY{ 1, 1 } }, PALETTE_INDEX_21);
|
||||
}
|
||||
}
|
||||
|
||||
lastCoords = coords;
|
||||
lastCoordsValid = true;
|
||||
}
|
||||
coords.x += xStep;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawFinanceGraph(DrawPixelInfo& dpi, const GraphProperties<money64>& p)
|
||||
{
|
||||
const FmtString fmt("{BLACK}{CURRENCY2DP} -");
|
||||
DrawYLabels<money64>(dpi, p.internalBounds, p.min, p.max, p.numYLabels, p.yLabelStepPx, p.lineCol, fmt);
|
||||
DrawMonths<money64, kMoney64Undefined>(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx);
|
||||
DrawLine<money64, kMoney64Undefined, true>(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max);
|
||||
DrawLine<money64, kMoney64Undefined, false>(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)
|
||||
{
|
||||
char buffer[64]{};
|
||||
FormatStringToBuffer(buffer, sizeof(buffer), "{CURRENCY2DP}", value);
|
||||
DrawHoveredValue<money64>(
|
||||
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<uint8_t>& p)
|
||||
{
|
||||
constexpr uint8_t noValue = ParkRatingHistoryUndefined;
|
||||
const FmtString fmt("{BLACK}{COMMA32} -");
|
||||
// Since the park rating rating history is divided by 4, we have to fudge the max number here.
|
||||
DrawYLabels<uint16_t>(dpi, p.internalBounds, p.min, 1000, p.numYLabels, p.yLabelStepPx, p.lineCol, fmt);
|
||||
DrawMonths<uint8_t, noValue>(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx);
|
||||
DrawLine<uint8_t, noValue, true>(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max);
|
||||
DrawLine<uint8_t, noValue, false>(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max);
|
||||
}
|
||||
|
||||
void DrawGuestGraph(DrawPixelInfo& dpi, const GraphProperties<uint32_t>& p)
|
||||
{
|
||||
constexpr uint32_t noValue = GuestsInParkHistoryUndefined;
|
||||
const FmtString fmt("{BLACK}{COMMA32} -");
|
||||
DrawYLabels<uint32_t>(dpi, p.internalBounds, p.min, p.max, p.numYLabels, p.yLabelStepPx, p.lineCol, fmt);
|
||||
DrawMonths<uint32_t, noValue>(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx);
|
||||
DrawLine<uint32_t, noValue, true>(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max);
|
||||
DrawLine<uint32_t, noValue, false>(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max);
|
||||
}
|
||||
} // namespace OpenRCT2::Graph
|
||||
|
||||
@@ -9,14 +9,62 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <openrct2/Context.h>
|
||||
#include <openrct2/core/Money.hpp>
|
||||
#include <openrct2/drawing/Drawing.h>
|
||||
#include <openrct2/world/Location.hpp>
|
||||
|
||||
namespace OpenRCT2::Graph
|
||||
{
|
||||
void Draw(DrawPixelInfo& dpi, uint8_t* history, int32_t count, const ScreenCoordsXY& screenPos);
|
||||
void Draw(
|
||||
DrawPixelInfo& dpi, const money64* history, const int32_t count, const ScreenCoordsXY& coords, const int32_t modifier,
|
||||
const int32_t offset);
|
||||
template<typename T> struct GraphProperties
|
||||
{
|
||||
ScreenRect internalBounds;
|
||||
const T* series;
|
||||
T min;
|
||||
T max;
|
||||
int32_t hoverIdx;
|
||||
int32_t numPoints;
|
||||
int32_t numYLabels;
|
||||
int32_t xStepPx;
|
||||
int32_t yLabelStepPx;
|
||||
ColourWithFlags lineCol;
|
||||
|
||||
void RecalculateLayout(const ScreenRect newBounds, const int32_t newNumYLabels, const int32_t newNumPoints)
|
||||
{
|
||||
yLabelStepPx = (newBounds.GetBottom() - newBounds.GetTop()) / (newNumYLabels - 1);
|
||||
xStepPx = (newBounds.GetRight() - newBounds.GetLeft()) / (newNumPoints - 1);
|
||||
// adjust bounds to be exact multiples of the steps.
|
||||
internalBounds = { newBounds.Point1,
|
||||
newBounds.Point1
|
||||
+ ScreenCoordsXY{ xStepPx * (newNumPoints - 1), yLabelStepPx * (newNumYLabels - 1) } };
|
||||
numPoints = newNumPoints;
|
||||
numYLabels = newNumYLabels;
|
||||
}
|
||||
|
||||
bool UpdateHoverIndex()
|
||||
{
|
||||
const ScreenCoordsXY cursorPos = ContextGetCursorPositionScaled();
|
||||
|
||||
int32_t i = -1;
|
||||
if (internalBounds.Contains(cursorPos))
|
||||
{
|
||||
i = (numPoints - 1) - (cursorPos.x - internalBounds.GetLeft() + (xStepPx / 2)) / xStepPx;
|
||||
if (i < 0)
|
||||
i = 1;
|
||||
if (i > numPoints - 1)
|
||||
i = numPoints - 1;
|
||||
}
|
||||
|
||||
if (hoverIdx != i)
|
||||
{
|
||||
hoverIdx = i;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void DrawFinanceGraph(DrawPixelInfo& dpi, const GraphProperties<money64>& p);
|
||||
void DrawRatingGraph(DrawPixelInfo& dpi, const GraphProperties<uint8_t>& p);
|
||||
void DrawGuestGraph(DrawPixelInfo& dpi, const GraphProperties<uint32_t>& p);
|
||||
} // namespace OpenRCT2::Graph
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <openrct2-ui/interface/Widget.h>
|
||||
#include <openrct2-ui/windows/Window.h>
|
||||
#include <openrct2/Context.h>
|
||||
#include <openrct2/Game.h>
|
||||
#include <openrct2/GameState.h>
|
||||
#include <openrct2/actions/ParkSetLoanAction.h>
|
||||
#include <openrct2/actions/ParkSetResearchFundingAction.h>
|
||||
@@ -178,6 +177,8 @@ static Widget _windowFinancesResearchWidgets[] =
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Constants
|
||||
|
||||
static constexpr StringId _windowFinancesSummaryRowLabels[EnumValue(ExpenditureType::Count)] = {
|
||||
STR_FINANCES_SUMMARY_RIDE_CONSTRUCTION,
|
||||
STR_FINANCES_SUMMARY_RIDE_RUNNING_COSTS,
|
||||
@@ -218,12 +219,19 @@ static Widget _windowFinancesResearchWidgets[] =
|
||||
};
|
||||
static_assert(std::size(_windowFinancesPageHoldDownWidgets) == WINDOW_FINANCES_PAGE_COUNT);
|
||||
|
||||
static constexpr ScreenCoordsXY kGraphTopLeftPadding{ 88, 20 };
|
||||
static constexpr ScreenCoordsXY kGraphBottomRightPadding{ 15, 18 };
|
||||
static constexpr uint8_t kGraphNumYLabels = 5;
|
||||
static constexpr int32_t kGraphNumPoints = 64;
|
||||
|
||||
#pragma endregion
|
||||
|
||||
class FinancesWindow final : public Window
|
||||
{
|
||||
private:
|
||||
uint32_t _lastPaintedMonth;
|
||||
uint32_t _lastPaintedMonth = std::numeric_limits<uint32_t>::max();
|
||||
ScreenRect _graphBounds;
|
||||
Graph::GraphProperties<money64> _graphProps{};
|
||||
|
||||
void SetDisabledTabs()
|
||||
{
|
||||
@@ -236,12 +244,22 @@ static Widget _windowFinancesResearchWidgets[] =
|
||||
SetPage(WINDOW_FINANCES_PAGE_SUMMARY);
|
||||
_lastPaintedMonth = std::numeric_limits<uint32_t>::max();
|
||||
ResearchUpdateUncompletedTypes();
|
||||
_graphProps.hoverIdx = -1;
|
||||
}
|
||||
|
||||
void OnUpdate() override
|
||||
{
|
||||
frame_no++;
|
||||
InvalidateWidget(WIDX_TAB_1 + page);
|
||||
|
||||
if (page == WINDOW_FINANCES_PAGE_VALUE_GRAPH || page == WINDOW_FINANCES_PAGE_PROFIT_GRAPH
|
||||
|| page == WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH)
|
||||
{
|
||||
if (_graphProps.UpdateHoverIndex())
|
||||
{
|
||||
InvalidateWidget(WIDX_BACKGROUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnMouseDown(WidgetIndex widgetIndex) override
|
||||
@@ -319,6 +337,34 @@ static Widget _windowFinancesResearchWidgets[] =
|
||||
case WINDOW_FINANCES_PAGE_RESEARCH:
|
||||
WindowResearchFundingPrepareDraw(this, WIDX_RESEARCH_FUNDING);
|
||||
break;
|
||||
case WINDOW_FINANCES_PAGE_VALUE_GRAPH:
|
||||
case WINDOW_FINANCES_PAGE_PROFIT_GRAPH:
|
||||
case WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH:
|
||||
{
|
||||
// recalculate size for graph pages only.
|
||||
Widget* pageWidget;
|
||||
switch (page)
|
||||
{
|
||||
case WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH:
|
||||
pageWidget = &_windowFinancesCashWidgets[WIDX_PAGE_BACKGROUND];
|
||||
break;
|
||||
case WINDOW_FINANCES_PAGE_VALUE_GRAPH:
|
||||
pageWidget = &_windowFinancesParkValueWidgets[WIDX_PAGE_BACKGROUND];
|
||||
break;
|
||||
case WINDOW_FINANCES_PAGE_PROFIT_GRAPH:
|
||||
pageWidget = &_windowFinancesProfitWidgets[WIDX_PAGE_BACKGROUND];
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
_graphBounds = { windowPos + ScreenCoordsXY{ pageWidget->left + 4, pageWidget->top + 15 },
|
||||
windowPos + ScreenCoordsXY{ pageWidget->right - 4, pageWidget->bottom - 4 } };
|
||||
_graphProps.RecalculateLayout(
|
||||
{ _graphBounds.Point1 + kGraphTopLeftPadding, _graphBounds.Point2 - kGraphBottomRightPadding },
|
||||
kGraphNumYLabels, kGraphNumPoints);
|
||||
_graphProps.lineCol = colours[2];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,14 +379,28 @@ static Widget _windowFinancesResearchWidgets[] =
|
||||
OnDrawSummary(dpi);
|
||||
break;
|
||||
case WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH:
|
||||
OnDrawFinancialGraph(dpi);
|
||||
{
|
||||
auto& gameState = GetGameState();
|
||||
auto cashLessLoan = gameState.Cash - gameState.BankLoan;
|
||||
auto fmt = cashLessLoan >= 0 ? STR_FINANCES_FINANCIAL_GRAPH_CASH_LESS_LOAN_POSITIVE
|
||||
: STR_FINANCES_FINANCIAL_GRAPH_CASH_LESS_LOAN_NEGATIVE;
|
||||
OnDrawGraph(dpi, cashLessLoan, gameState.CashHistory, fmt, true);
|
||||
break;
|
||||
}
|
||||
case WINDOW_FINANCES_PAGE_VALUE_GRAPH:
|
||||
OnDrawParkValueGraph(dpi);
|
||||
{
|
||||
auto& gameState = GetGameState();
|
||||
OnDrawGraph(dpi, gameState.Park.Value, gameState.Park.ValueHistory, STR_FINANCES_PARK_VALUE, false);
|
||||
break;
|
||||
}
|
||||
case WINDOW_FINANCES_PAGE_PROFIT_GRAPH:
|
||||
OnDrawProfitGraph(dpi);
|
||||
{
|
||||
auto& gameState = GetGameState();
|
||||
auto fmt = gameState.CurrentProfit >= 0 ? STR_FINANCES_WEEKLY_PROFIT_POSITIVE
|
||||
: STR_FINANCES_WEEKLY_PROFIT_LOSS;
|
||||
OnDrawGraph(dpi, gameState.CurrentProfit, gameState.WeeklyProfitHistory, fmt, true);
|
||||
break;
|
||||
}
|
||||
case WINDOW_FINANCES_PAGE_MARKETING:
|
||||
OnDrawMarketing(dpi);
|
||||
break;
|
||||
@@ -456,16 +516,26 @@ static Widget _windowFinancesResearchWidgets[] =
|
||||
{
|
||||
width = WW_RESEARCH;
|
||||
height = WH_RESEARCH;
|
||||
flags &= ~WF_RESIZABLE;
|
||||
}
|
||||
else if (p == WINDOW_FINANCES_PAGE_SUMMARY)
|
||||
{
|
||||
width = WW_OTHER_TABS;
|
||||
height = WH_SUMMARY;
|
||||
flags &= ~WF_RESIZABLE;
|
||||
}
|
||||
else if (
|
||||
p == WINDOW_FINANCES_PAGE_VALUE_GRAPH || p == WINDOW_FINANCES_PAGE_PROFIT_GRAPH
|
||||
|| p == WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH)
|
||||
{
|
||||
flags |= WF_RESIZABLE;
|
||||
WindowSetResize(*this, WW_OTHER_TABS, WH_OTHER_TABS, 2000, 2000);
|
||||
}
|
||||
else
|
||||
{
|
||||
width = WW_OTHER_TABS;
|
||||
height = WH_OTHER_TABS;
|
||||
flags &= ~WF_RESIZABLE;
|
||||
}
|
||||
OnResize();
|
||||
OnPrepareDraw();
|
||||
@@ -607,190 +677,6 @@ static Widget _windowFinancesResearchWidgets[] =
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Financial Graph Events
|
||||
|
||||
void OnDrawFinancialGraph(DrawPixelInfo& dpi)
|
||||
{
|
||||
Widget* pageWidget = &_windowFinancesCashWidgets[WIDX_PAGE_BACKGROUND];
|
||||
auto graphTopLeft = windowPos + ScreenCoordsXY{ pageWidget->left + 4, pageWidget->top + 15 };
|
||||
auto graphBottomRight = windowPos + ScreenCoordsXY{ pageWidget->right - 4, pageWidget->bottom - 4 };
|
||||
|
||||
const auto& gameState = GetGameState();
|
||||
|
||||
// Cash (less loan)
|
||||
auto cashLessLoan = gameState.Cash - gameState.BankLoan;
|
||||
auto ft = Formatter();
|
||||
ft.Add<money64>(cashLessLoan);
|
||||
|
||||
DrawTextBasic(
|
||||
dpi, graphTopLeft - ScreenCoordsXY{ 0, 11 },
|
||||
cashLessLoan >= 0 ? STR_FINANCES_FINANCIAL_GRAPH_CASH_LESS_LOAN_POSITIVE
|
||||
: STR_FINANCES_FINANCIAL_GRAPH_CASH_LESS_LOAN_NEGATIVE,
|
||||
ft);
|
||||
|
||||
// Graph
|
||||
GfxFillRectInset(dpi, { graphTopLeft, graphBottomRight }, colours[1], INSET_RECT_F_30);
|
||||
|
||||
// Calculate the Y axis scale (log2 of highest [+/-]balance)
|
||||
int32_t yAxisScale = 0;
|
||||
for (int32_t i = 0; i < 64; i++)
|
||||
{
|
||||
auto balance = gameState.CashHistory[i];
|
||||
if (balance == kMoney64Undefined)
|
||||
continue;
|
||||
|
||||
// Modifier balance then keep halving until less than 127 pixels
|
||||
balance = std::abs(balance) >> yAxisScale;
|
||||
while (balance > 127)
|
||||
{
|
||||
balance /= 2;
|
||||
yAxisScale++;
|
||||
}
|
||||
}
|
||||
|
||||
// Y axis labels
|
||||
auto coords = graphTopLeft + ScreenCoordsXY{ 18, 14 };
|
||||
money64 axisBase;
|
||||
for (axisBase = 12.00_GBP; axisBase >= -12.00_GBP; axisBase -= 6.00_GBP)
|
||||
{
|
||||
auto axisValue = axisBase << yAxisScale;
|
||||
ft = Formatter();
|
||||
ft.Add<money64>(axisValue);
|
||||
DrawTextBasic(
|
||||
dpi, coords + ScreenCoordsXY{ 70, 0 }, STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE, ft,
|
||||
{ FontStyle::Small, TextAlignment::RIGHT });
|
||||
GfxFillRectInset(
|
||||
dpi, { coords + ScreenCoordsXY{ 70, 5 }, { graphTopLeft.x + 482, coords.y + 5 } }, colours[2],
|
||||
INSET_RECT_FLAG_BORDER_INSET);
|
||||
coords.y += 39;
|
||||
}
|
||||
|
||||
// X axis labels and values
|
||||
coords = graphTopLeft + ScreenCoordsXY{ 98, 17 };
|
||||
Graph::Draw(dpi, gameState.CashHistory, 64, coords, yAxisScale, 128);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Park Value Graph Events
|
||||
|
||||
void OnDrawParkValueGraph(DrawPixelInfo& dpi)
|
||||
{
|
||||
Widget* pageWidget = &_windowFinancesCashWidgets[WIDX_PAGE_BACKGROUND];
|
||||
auto graphTopLeft = windowPos + ScreenCoordsXY{ pageWidget->left + 4, pageWidget->top + 15 };
|
||||
auto graphBottomRight = windowPos + ScreenCoordsXY{ pageWidget->right - 4, pageWidget->bottom - 4 };
|
||||
|
||||
const auto& gameState = GetGameState();
|
||||
|
||||
// Park value
|
||||
auto ft = Formatter();
|
||||
ft.Add<money64>(gameState.Park.Value);
|
||||
DrawTextBasic(dpi, graphTopLeft - ScreenCoordsXY{ 0, 11 }, STR_FINANCES_PARK_VALUE, ft);
|
||||
|
||||
// Graph
|
||||
GfxFillRectInset(dpi, { graphTopLeft, graphBottomRight }, colours[1], INSET_RECT_F_30);
|
||||
|
||||
// Calculate the Y axis scale (log2 of highest [+/-]balance)
|
||||
int32_t yAxisScale = 0;
|
||||
for (int32_t i = 0; i < 64; i++)
|
||||
{
|
||||
auto balance = gameState.Park.ValueHistory[i];
|
||||
if (balance == kMoney64Undefined)
|
||||
continue;
|
||||
|
||||
// Modifier balance then keep halving until less than 255 pixels
|
||||
balance = std::abs(balance) >> yAxisScale;
|
||||
while (balance > 255)
|
||||
{
|
||||
balance /= 2;
|
||||
yAxisScale++;
|
||||
}
|
||||
}
|
||||
|
||||
// Y axis labels
|
||||
auto coords = graphTopLeft + ScreenCoordsXY{ 18, 14 };
|
||||
money64 axisBase;
|
||||
for (axisBase = 24.00_GBP; axisBase >= 0.00_GBP; axisBase -= 6.00_GBP)
|
||||
{
|
||||
auto axisValue = axisBase << yAxisScale;
|
||||
ft = Formatter();
|
||||
ft.Add<money64>(axisValue);
|
||||
DrawTextBasic(
|
||||
dpi, coords + ScreenCoordsXY{ 70, 0 }, STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE, ft,
|
||||
{ FontStyle::Small, TextAlignment::RIGHT });
|
||||
GfxFillRectInset(
|
||||
dpi, { coords + ScreenCoordsXY{ 70, 5 }, { graphTopLeft.x + 482, coords.y + 5 } }, colours[2],
|
||||
INSET_RECT_FLAG_BORDER_INSET);
|
||||
coords.y += 39;
|
||||
}
|
||||
|
||||
// X axis labels and values
|
||||
coords = graphTopLeft + ScreenCoordsXY{ 98, 17 };
|
||||
Graph::Draw(dpi, gameState.Park.ValueHistory, 64, coords, yAxisScale, 0);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Profit Graph Events
|
||||
|
||||
void OnDrawProfitGraph(DrawPixelInfo& dpi)
|
||||
{
|
||||
auto& gameState = GetGameState();
|
||||
Widget* pageWidget = &_windowFinancesCashWidgets[WIDX_PAGE_BACKGROUND];
|
||||
auto graphTopLeft = windowPos + ScreenCoordsXY{ pageWidget->left + 4, pageWidget->top + 15 };
|
||||
auto graphBottomRight = windowPos + ScreenCoordsXY{ pageWidget->right - 4, pageWidget->bottom - 4 };
|
||||
|
||||
// Weekly profit
|
||||
auto ft = Formatter();
|
||||
ft.Add<money64>(gameState.CurrentProfit);
|
||||
DrawTextBasic(
|
||||
dpi, graphTopLeft - ScreenCoordsXY{ 0, 11 },
|
||||
gameState.CurrentProfit >= 0 ? STR_FINANCES_WEEKLY_PROFIT_POSITIVE : STR_FINANCES_WEEKLY_PROFIT_LOSS, ft);
|
||||
|
||||
// Graph
|
||||
GfxFillRectInset(dpi, { graphTopLeft, graphBottomRight }, colours[1], INSET_RECT_F_30);
|
||||
|
||||
// Calculate the Y axis scale (log2 of highest [+/-]balance)
|
||||
int32_t yAxisScale = 0;
|
||||
for (int32_t i = 0; i < 64; i++)
|
||||
{
|
||||
auto balance = gameState.WeeklyProfitHistory[i];
|
||||
if (balance == kMoney64Undefined)
|
||||
continue;
|
||||
|
||||
// Modifier balance then keep halving until less than 127 pixels
|
||||
balance = std::abs(balance) >> yAxisScale;
|
||||
while (balance > 127)
|
||||
{
|
||||
balance /= 2;
|
||||
yAxisScale++;
|
||||
}
|
||||
}
|
||||
|
||||
// Y axis labels
|
||||
auto screenPos = graphTopLeft + ScreenCoordsXY{ 18, 14 };
|
||||
money64 axisBase;
|
||||
for (axisBase = 12.00_GBP; axisBase >= -12.00_GBP; axisBase -= 6.00_GBP)
|
||||
{
|
||||
money64 axisValue = axisBase << yAxisScale;
|
||||
ft = Formatter();
|
||||
ft.Add<money64>(axisValue);
|
||||
DrawTextBasic(
|
||||
dpi, screenPos + ScreenCoordsXY{ 70, 0 }, STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE, ft,
|
||||
{ FontStyle::Small, TextAlignment::RIGHT });
|
||||
GfxFillRectInset(
|
||||
dpi, { screenPos + ScreenCoordsXY{ 70, 5 }, { graphTopLeft.x + 482, screenPos.y + 5 } }, colours[2],
|
||||
INSET_RECT_FLAG_BORDER_INSET);
|
||||
screenPos.y += 39;
|
||||
}
|
||||
|
||||
// X axis labels and values
|
||||
screenPos = graphTopLeft + ScreenCoordsXY{ 98, 17 };
|
||||
Graph::Draw(dpi, gameState.WeeklyProfitHistory, 64, screenPos, yAxisScale, 128);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Marketing Events
|
||||
|
||||
void OnMouseUpMarketing(WidgetIndex widgetIndex)
|
||||
@@ -951,6 +837,47 @@ static Widget _windowFinancesResearchWidgets[] =
|
||||
{
|
||||
ResizeFrameWithPage();
|
||||
}
|
||||
|
||||
void OnDrawGraph(
|
||||
DrawPixelInfo& dpi, const money64 currentValue, money64 (&series)[kFinanceHistorySize], const StringId fmt,
|
||||
const bool centred)
|
||||
{
|
||||
Formatter ft;
|
||||
ft.Add<money64>(currentValue);
|
||||
DrawTextBasic(dpi, _graphBounds.Point1 - ScreenCoordsXY{ 0, 11 }, fmt, ft);
|
||||
|
||||
// Graph
|
||||
GfxFillRectInset(dpi, _graphBounds, colours[1], INSET_RECT_F_30);
|
||||
// hide resize widget on graph area
|
||||
constexpr ScreenCoordsXY offset{ 1, 1 };
|
||||
constexpr ScreenCoordsXY bigOffset{ 5, 5 };
|
||||
GfxFillRectInset(
|
||||
dpi, { _graphBounds.Point2 - bigOffset, _graphBounds.Point2 - offset }, colours[1],
|
||||
INSET_RECT_FLAG_FILL_DONT_LIGHTEN | INSET_RECT_FLAG_BORDER_NONE);
|
||||
|
||||
// Calculate Y axis max and min.
|
||||
money64 maxVal = 0;
|
||||
for (int32_t i = 0; i < kGraphNumPoints; i++)
|
||||
{
|
||||
auto val = series[i];
|
||||
if (val == kMoney64Undefined)
|
||||
continue;
|
||||
while (std::abs(val) > maxVal)
|
||||
maxVal = val;
|
||||
}
|
||||
// This algorithm increments the leading digit of the max and sets all other digits to zero.
|
||||
// e.g. 681 => 700.
|
||||
money64 oom = 10;
|
||||
while (maxVal / oom >= 10)
|
||||
oom *= 10;
|
||||
const money64 max = ((maxVal + oom - 1) / oom) * oom;
|
||||
|
||||
_graphProps.min = centred ? -max : 0.00_GBP;
|
||||
_graphProps.max = max;
|
||||
_graphProps.series = series;
|
||||
|
||||
Graph::DrawFinanceGraph(dpi, _graphProps);
|
||||
}
|
||||
};
|
||||
|
||||
static FinancesWindow* FinancesWindowOpen(uint8_t page)
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "../interface/Theme.h"
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <openrct2-ui/interface/Dropdown.h>
|
||||
#include <openrct2-ui/interface/Graph.h>
|
||||
#include <openrct2-ui/interface/LandTool.h>
|
||||
@@ -27,13 +26,11 @@
|
||||
#include <openrct2/config/Config.h>
|
||||
#include <openrct2/localisation/Currency.h>
|
||||
#include <openrct2/localisation/Formatting.h>
|
||||
#include <openrct2/localisation/Localisation.Date.h>
|
||||
#include <openrct2/management/Award.h>
|
||||
#include <openrct2/peep/PeepAnimationData.h>
|
||||
#include <openrct2/ride/RideData.h>
|
||||
#include <openrct2/scenario/Scenario.h>
|
||||
#include <openrct2/util/Util.h>
|
||||
#include <openrct2/world/Entrance.h>
|
||||
#include <openrct2/world/Park.h>
|
||||
|
||||
namespace OpenRCT2::Ui::Windows
|
||||
@@ -41,6 +38,10 @@ 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 uint8_t kGraphNumYLabels = 6;
|
||||
|
||||
// clang-format off
|
||||
enum WindowParkPage {
|
||||
WINDOW_PARK_PAGE_ENTRANCE,
|
||||
@@ -197,6 +198,12 @@ static constexpr WindowParkAward _parkAwards[] = {
|
||||
int32_t _numberOfRides = -1;
|
||||
uint8_t _peepAnimationFrame = 0;
|
||||
|
||||
Graph::GraphProperties<uint8_t> _ratingProps{};
|
||||
Graph::GraphProperties<uint32_t> _guestProps{};
|
||||
|
||||
ScreenRect _ratingGraphBounds;
|
||||
ScreenRect _guestGraphBounds;
|
||||
|
||||
public:
|
||||
void OnOpen() override
|
||||
{
|
||||
@@ -206,6 +213,11 @@ static constexpr WindowParkAward _parkAwards[] = {
|
||||
_numberOfStaff = -1;
|
||||
_peepAnimationFrame = 0;
|
||||
SetPage(0);
|
||||
|
||||
_ratingProps.lineCol = colours[2];
|
||||
_guestProps.lineCol = colours[2];
|
||||
_ratingProps.hoverIdx = -1;
|
||||
_guestProps.hoverIdx = -1;
|
||||
}
|
||||
|
||||
void OnClose() override
|
||||
@@ -691,6 +703,13 @@ static constexpr WindowParkAward _parkAwards[] = {
|
||||
|
||||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7);
|
||||
AnchorBorderWidgets();
|
||||
|
||||
const Widget* background = &widgets[WIDX_PAGE_BACKGROUND];
|
||||
_ratingGraphBounds = { windowPos + ScreenCoordsXY{ background->left + 4, background->top + 15 },
|
||||
windowPos + ScreenCoordsXY{ background->right - 4, background->bottom - 4 } };
|
||||
_ratingProps.RecalculateLayout(
|
||||
{ _ratingGraphBounds.Point1 + kGraphTopLeftPadding, _ratingGraphBounds.Point2 - kGraphBottomRightPadding },
|
||||
kGraphNumYLabels, kParkRatingHistorySize);
|
||||
}
|
||||
|
||||
void OnDrawRating(DrawPixelInfo& dpi)
|
||||
@@ -698,41 +717,21 @@ static constexpr WindowParkAward _parkAwards[] = {
|
||||
DrawWidgets(dpi);
|
||||
DrawTabImages(dpi);
|
||||
|
||||
auto screenPos = windowPos;
|
||||
Widget* widget = &widgets[WIDX_PAGE_BACKGROUND];
|
||||
const auto& gameState = OpenRCT2::GetGameState();
|
||||
|
||||
// Current value
|
||||
auto ft = Formatter();
|
||||
ft.Add<uint16_t>(GetGameState().Park.Rating);
|
||||
DrawTextBasic(dpi, screenPos + ScreenCoordsXY{ widget->left + 3, widget->top + 2 }, STR_PARK_RATING_LABEL, ft);
|
||||
Formatter ft;
|
||||
ft.Add<uint16_t>(gameState.Park.Rating);
|
||||
DrawTextBasic(dpi, windowPos + ScreenCoordsXY{ widget->left + 3, widget->top + 2 }, STR_PARK_RATING_LABEL, ft);
|
||||
|
||||
// Graph border
|
||||
GfxFillRectInset(
|
||||
dpi,
|
||||
{ screenPos + ScreenCoordsXY{ widget->left + 4, widget->top + 15 },
|
||||
screenPos + ScreenCoordsXY{ widget->right - 4, widget->bottom - 4 } },
|
||||
colours[1], INSET_RECT_F_30);
|
||||
GfxFillRectInset(dpi, _ratingGraphBounds, colours[1], INSET_RECT_F_30);
|
||||
|
||||
// Y axis labels
|
||||
screenPos = screenPos + ScreenCoordsXY{ widget->left + 27, widget->top + 23 };
|
||||
for (int i = 5; i >= 0; i--)
|
||||
{
|
||||
uint32_t axisValue = i * 200;
|
||||
ft = Formatter();
|
||||
ft.Add<uint32_t>(axisValue);
|
||||
DrawTextBasic(
|
||||
dpi, screenPos + ScreenCoordsXY{ 10, 0 }, STR_GRAPH_AXIS_LABEL, ft,
|
||||
{ FontStyle::Small, TextAlignment::RIGHT });
|
||||
GfxFillRectInset(
|
||||
dpi, { screenPos + ScreenCoordsXY{ 15, 5 }, screenPos + ScreenCoordsXY{ width - 32, 5 } }, colours[2],
|
||||
INSET_RECT_FLAG_BORDER_INSET);
|
||||
screenPos.y += 20;
|
||||
}
|
||||
|
||||
// Graph
|
||||
screenPos = windowPos + ScreenCoordsXY{ widget->left + 47, widget->top + 26 };
|
||||
|
||||
Graph::Draw(dpi, GetGameState().Park.RatingHistory, kParkRatingHistorySize, screenPos);
|
||||
_ratingProps.min = 0;
|
||||
_ratingProps.max = 250;
|
||||
_ratingProps.series = gameState.Park.RatingHistory;
|
||||
Graph::DrawRatingGraph(dpi, _ratingProps);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
@@ -764,6 +763,13 @@ static constexpr WindowParkAward _parkAwards[] = {
|
||||
|
||||
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7);
|
||||
AnchorBorderWidgets();
|
||||
|
||||
const Widget* background = &widgets[WIDX_PAGE_BACKGROUND];
|
||||
_guestGraphBounds = { windowPos + ScreenCoordsXY{ background->left + 4, background->top + 15 },
|
||||
windowPos + ScreenCoordsXY{ background->right - 4, background->bottom - 4 } };
|
||||
_guestProps.RecalculateLayout(
|
||||
{ _guestGraphBounds.Point1 + kGraphTopLeftPadding, _guestGraphBounds.Point2 - kGraphBottomRightPadding },
|
||||
kGraphNumYLabels, kGuestsInParkHistorySize);
|
||||
}
|
||||
|
||||
void OnDrawGuests(DrawPixelInfo& dpi)
|
||||
@@ -771,56 +777,30 @@ static constexpr WindowParkAward _parkAwards[] = {
|
||||
DrawWidgets(dpi);
|
||||
DrawTabImages(dpi);
|
||||
|
||||
auto screenPos = windowPos;
|
||||
Widget* widget = &widgets[WIDX_PAGE_BACKGROUND];
|
||||
|
||||
const auto& gameState = OpenRCT2::GetGameState();
|
||||
|
||||
// Current value
|
||||
auto ft = Formatter();
|
||||
Formatter ft;
|
||||
ft.Add<uint32_t>(gameState.NumGuestsInPark);
|
||||
DrawTextBasic(dpi, screenPos + ScreenCoordsXY{ widget->left + 3, widget->top + 2 }, STR_GUESTS_IN_PARK_LABEL, ft);
|
||||
DrawTextBasic(dpi, windowPos + ScreenCoordsXY{ widget->left + 3, widget->top + 2 }, STR_GUESTS_IN_PARK_LABEL, ft);
|
||||
|
||||
// Graph border
|
||||
GfxFillRectInset(
|
||||
dpi,
|
||||
{ screenPos + ScreenCoordsXY{ widget->left + 4, widget->top + 15 },
|
||||
screenPos + ScreenCoordsXY{ widget->right - 4, widget->bottom - 4 } },
|
||||
colours[1], INSET_RECT_F_30);
|
||||
GfxFillRectInset(dpi, _guestGraphBounds, colours[1], INSET_RECT_F_30);
|
||||
|
||||
// Y axis labels
|
||||
screenPos = screenPos + ScreenCoordsXY{ widget->left + 27, widget->top + 23 };
|
||||
for (int i = 5; i >= 0; i--)
|
||||
{
|
||||
uint32_t axisValue = i * 1000;
|
||||
ft = Formatter();
|
||||
ft.Add<uint32_t>(axisValue);
|
||||
DrawTextBasic(
|
||||
dpi, screenPos + ScreenCoordsXY{ 10, 0 }, STR_GRAPH_AXIS_LABEL, ft,
|
||||
{ FontStyle::Small, TextAlignment::RIGHT });
|
||||
GfxFillRectInset(
|
||||
dpi, { screenPos + ScreenCoordsXY{ 15, 5 }, screenPos + ScreenCoordsXY{ width - 32, 5 } }, colours[2],
|
||||
INSET_RECT_FLAG_BORDER_INSET);
|
||||
screenPos.y += 20;
|
||||
}
|
||||
|
||||
// Graph
|
||||
screenPos = windowPos + ScreenCoordsXY{ widget->left + 47, widget->top + 26 };
|
||||
|
||||
uint8_t cappedHistory[32];
|
||||
for (size_t i = 0; i < std::size(cappedHistory); i++)
|
||||
// Calculate Y axis max and min
|
||||
_guestProps.min = 0;
|
||||
_guestProps.max = 5000;
|
||||
for (size_t i = 0; i < std::size(gameState.GuestsInParkHistory); i++)
|
||||
{
|
||||
auto value = gameState.GuestsInParkHistory[i];
|
||||
if (value != std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
cappedHistory[i] = static_cast<uint8_t>(std::min<uint32_t>(value, 5000) / 20);
|
||||
}
|
||||
else
|
||||
{
|
||||
cappedHistory[i] = std::numeric_limits<uint8_t>::max();
|
||||
}
|
||||
if (value == GuestsInParkHistoryUndefined)
|
||||
continue;
|
||||
while (value > _guestProps.max)
|
||||
_guestProps.max += 5000;
|
||||
}
|
||||
Graph::Draw(dpi, cappedHistory, static_cast<int32_t>(std::size(cappedHistory)), screenPos);
|
||||
_guestProps.series = gameState.GuestsInParkHistory;
|
||||
Graph::DrawGuestGraph(dpi, _guestProps);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
@@ -1313,6 +1293,7 @@ static constexpr WindowParkAward _parkAwards[] = {
|
||||
windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_7].left, widgets[WIDX_TAB_7].top });
|
||||
}
|
||||
}
|
||||
#pragma endregion
|
||||
};
|
||||
|
||||
static ParkWindow* ParkWindowOpen(uint8_t page)
|
||||
|
||||
@@ -47,13 +47,13 @@ namespace OpenRCT2
|
||||
money64 ConstructionRightsPrice;
|
||||
money64 CurrentExpenditure;
|
||||
money64 CurrentProfit;
|
||||
uint32_t GuestsInParkHistory[32];
|
||||
uint32_t GuestsInParkHistory[kGuestsInParkHistorySize];
|
||||
ClimateType Climate;
|
||||
ClimateState ClimateCurrent;
|
||||
ClimateState ClimateNext;
|
||||
uint16_t ClimateUpdateTimer;
|
||||
money64 Cash;
|
||||
money64 CashHistory[kFinanceGraphSize];
|
||||
money64 CashHistory[kFinanceHistorySize];
|
||||
money64 InitialCash;
|
||||
money64 GuestInitialCash;
|
||||
uint8_t GuestInitialHappiness;
|
||||
@@ -69,7 +69,7 @@ namespace OpenRCT2
|
||||
money64 TotalIncomeFromAdmissions;
|
||||
money64 TotalRideValueForMoney;
|
||||
uint16_t WeeklyProfitAverageDivisor;
|
||||
money64 WeeklyProfitHistory[kFinanceGraphSize];
|
||||
money64 WeeklyProfitHistory[kFinanceHistorySize];
|
||||
Objective ScenarioObjective;
|
||||
uint16_t ScenarioParkRatingWarningDays;
|
||||
money64 ScenarioCompletedCompanyValue;
|
||||
|
||||
@@ -185,7 +185,7 @@ void FinancePayRideUpkeep()
|
||||
void FinanceResetHistory()
|
||||
{
|
||||
auto& gameState = GetGameState();
|
||||
for (auto i = 0; i < kFinanceGraphSize; i++)
|
||||
for (auto i = 0; i < kFinanceHistorySize; i++)
|
||||
{
|
||||
gameState.CashHistory[i] = kMoney64Undefined;
|
||||
gameState.WeeklyProfitHistory[i] = kMoney64Undefined;
|
||||
|
||||
@@ -32,7 +32,7 @@ enum class ExpenditureType : int32_t
|
||||
};
|
||||
|
||||
constexpr uint8_t kExpenditureTableMonthCount = 16;
|
||||
constexpr uint8_t kFinanceGraphSize = 128;
|
||||
constexpr uint8_t kFinanceHistorySize = 128;
|
||||
|
||||
constexpr uint8_t MaxBankLoanInterestRate = 255;
|
||||
|
||||
|
||||
@@ -769,7 +769,7 @@ namespace OpenRCT2::RCT1
|
||||
uint8_t Unk1990AA[94];
|
||||
uint16_t ParkRating;
|
||||
uint8_t ParkRatingHistory[kParkRatingHistorySize];
|
||||
uint8_t GuestsInParkHistory[32];
|
||||
uint8_t GuestsInParkHistory[kGuestsInParkHistorySize];
|
||||
uint8_t ResearchPriority;
|
||||
uint8_t ResearchProgressStage;
|
||||
uint8_t LastResearchItem;
|
||||
|
||||
@@ -862,7 +862,7 @@ namespace OpenRCT2::RCT2
|
||||
|
||||
// Ignored in scenario
|
||||
uint8_t ParkRatingHistory[kParkRatingHistorySize];
|
||||
uint8_t GuestsInParkHistory[32];
|
||||
uint8_t GuestsInParkHistory[kGuestsInParkHistorySize];
|
||||
|
||||
// SC6[10]
|
||||
uint8_t ActiveResearchTypes;
|
||||
|
||||
@@ -586,8 +586,10 @@ namespace OpenRCT2::Park
|
||||
gameState.NumGuestsInParkLastWeek = gameState.NumGuestsInPark;
|
||||
|
||||
// Update park rating, guests in park and current cash history
|
||||
HistoryPushRecord<uint8_t, 32>(gameState.Park.RatingHistory, gameState.Park.Rating / 4);
|
||||
HistoryPushRecord<uint32_t, 32>(gameState.GuestsInParkHistory, gameState.NumGuestsInPark);
|
||||
constexpr auto ratingHistorySize = std::extent_v<decltype(ParkData::RatingHistory)>;
|
||||
HistoryPushRecord<uint8_t, ratingHistorySize>(gameState.Park.RatingHistory, gameState.Park.Rating / 4);
|
||||
constexpr auto numGuestsHistorySize = std::extent_v<decltype(GameState_t::GuestsInParkHistory)>;
|
||||
HistoryPushRecord<uint32_t, numGuestsHistorySize>(gameState.GuestsInParkHistory, gameState.NumGuestsInPark);
|
||||
|
||||
constexpr auto cashHistorySize = std::extent_v<decltype(GameState_t::CashHistory)>;
|
||||
HistoryPushRecord<money64, cashHistorySize>(gameState.CashHistory, FinanceGetCurrentCash() - gameState.BankLoan);
|
||||
|
||||
@@ -17,6 +17,7 @@ constexpr auto MAX_ENTRANCE_FEE = 999.00_GBP;
|
||||
constexpr uint8_t ParkRatingHistoryUndefined = std::numeric_limits<uint8_t>::max();
|
||||
constexpr uint32_t GuestsInParkHistoryUndefined = std::numeric_limits<uint32_t>::max();
|
||||
constexpr uint8_t kParkRatingHistorySize = 32;
|
||||
constexpr uint8_t kGuestsInParkHistorySize = 32;
|
||||
constexpr uint8_t ParkNameMaxLength = 128;
|
||||
constexpr uint8_t ScenarioNameMaxLength = 128;
|
||||
constexpr uint16_t ScenarioDetailsNameMaxLength = 256;
|
||||
@@ -65,7 +66,7 @@ namespace OpenRCT2
|
||||
std::vector<CoordsXYZD> Entrances;
|
||||
uint32_t Size;
|
||||
money64 Value;
|
||||
money64 ValueHistory[kFinanceGraphSize];
|
||||
money64 ValueHistory[kFinanceHistorySize];
|
||||
|
||||
bool IsOpen() const;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user