OpenRCT2/src/openrct2-ui/windows/Finances.cpp

985 lines
40 KiB
C++
Raw Normal View History

/*****************************************************************************
* Copyright (c) 2014-2024 OpenRCT2 developers
*
* For a complete list of all authors, please refer to contributors.md
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is licensed under the GNU General Public License version 3.
*****************************************************************************/
#include <algorithm>
2018-06-22 23:21:44 +02:00
#include <openrct2-ui/interface/Dropdown.h>
#include <openrct2-ui/interface/Graph.h>
#include <openrct2-ui/interface/Widget.h>
#include <openrct2-ui/windows/Window.h>
#include <openrct2/Context.h>
#include <openrct2/Game.h>
#include <openrct2/GameState.h>
Split actions hpp files into separate h and cpp files (#13548) * Split up SmallSceneryPlace/Remove Added undo function for Remove Scenery * Refactor: Balloon and Banner actions hpp=>h/cpp * Refactor: rename all action *.hpp files to *.cpp This is preparation for separation in later commits. Note that without the complete set of commits in this branch, the code will not build. * Refactor Clear, Climate, Custom, and Footpath actions hpp=>h/cpp * VSCode: add src subdirectories to includePath * Refactor Guest actions hpp=>h/cpp * Refactor Land actions hpp=>h/cpp * Refactor LargeScenery actions hpp=>h/cpp * Refactor Load, Maze, Network actions hpp=>h/cpp * Refactor Park actions hpp=>h/cpp * Refactor/style: move private function declarations in actions *.h Previous action .h files included private function declarations with private member variables, before public function declarations. This commit re-orders the header files to the following order: - public member variables - private member variables - public functions - private functions * Refactor Pause action hpp=>h/cpp * Refactor Peep, Place, Player actions hpp=>h/cpp * Refactor Ride actions hpp=>h/cpp * Refactor Scenario, Set*, Sign* actions hpp=>h/cpp * Refactor SmallScenerySetColourAction hpp=>h/cpp * Refactor Staff actions hpp=>h/cpp * Refactor Surface, Tile, Track* actions hpp=>h/cpp * Refactor Wall and Water actions hpp=>h/cpp * Fix various includes and other compile errors Update includes for tests. Move static function declarations to .h files Add explicit includes to various files that were previously implicit (the required header was a nested include in an action hpp file, and the action .h file does not include that header) Move RideSetStatus string enum to the cpp file to avoid unused imports * Xcode: modify project file for actions refactor * Cleanup whitespace and end-of-file newlines Co-authored-by: duncanspumpkin <duncans_pumpkin@hotmail.co.uk>
2020-12-10 07:39:10 +01:00
#include <openrct2/actions/ParkSetLoanAction.h>
#include <openrct2/actions/ParkSetResearchFundingAction.h>
2017-09-05 20:55:18 +02:00
#include <openrct2/config/Config.h>
2018-01-06 18:32:25 +01:00
#include <openrct2/localisation/Date.h>
2021-12-12 00:06:06 +01:00
#include <openrct2/localisation/Formatter.h>
2018-01-06 18:32:25 +01:00
#include <openrct2/localisation/Localisation.h>
2018-06-22 23:21:44 +02:00
#include <openrct2/management/Finance.h>
2018-01-10 00:00:09 +01:00
#include <openrct2/ride/RideData.h>
#include <openrct2/ride/ShopItem.h>
2018-03-19 23:28:40 +01:00
#include <openrct2/scenario/Scenario.h>
2018-06-22 23:21:44 +02:00
#include <openrct2/sprites.h>
2018-03-19 23:28:40 +01:00
#include <openrct2/world/Park.h>
namespace OpenRCT2::Ui::Windows
2018-01-04 04:43:34 +01:00
{
enum
{
WINDOW_FINANCES_PAGE_SUMMARY,
WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH,
WINDOW_FINANCES_PAGE_VALUE_GRAPH,
WINDOW_FINANCES_PAGE_PROFIT_GRAPH,
WINDOW_FINANCES_PAGE_MARKETING,
WINDOW_FINANCES_PAGE_RESEARCH,
WINDOW_FINANCES_PAGE_COUNT
};
enum
{
WIDX_BACKGROUND,
WIDX_TITLE,
WIDX_CLOSE,
WIDX_PAGE_BACKGROUND,
WIDX_TAB_1,
WIDX_TAB_2,
WIDX_TAB_3,
WIDX_TAB_4,
WIDX_TAB_5,
WIDX_TAB_6,
WIDX_TAB_CONTENT,
WIDX_SUMMARY_SCROLL = WIDX_TAB_CONTENT,
WIDX_LOAN,
WIDX_LOAN_INCREASE,
WIDX_LOAN_DECREASE,
WIDX_ACTIVE_CAMPAIGNS_GROUP = WIDX_TAB_CONTENT,
WIDX_CAMPAIGNS_AVAILABLE_GROUP,
WIDX_CAMPAIGN_1,
WIDX_CAMPAIGN_2,
WIDX_CAMPAIGN_3,
WIDX_CAMPAIGN_4,
WIDX_CAMPAIGN_5,
WIDX_CAMPAIGN_6,
WIDX_RESEARCH_FUNDING_GROUP = WIDX_TAB_CONTENT,
WIDX_RESEARCH_FUNDING,
WIDX_RESEARCH_FUNDING_DROPDOWN_BUTTON,
WIDX_RESEARCH_PRIORITIES_GROUP,
WIDX_TRANSPORT_RIDES,
WIDX_GENTLE_RIDES,
WIDX_ROLLER_COASTERS,
WIDX_THRILL_RIDES,
WIDX_WATER_RIDES,
WIDX_SHOPS_AND_STALLS,
WIDX_SCENERY_AND_THEMING,
};
2014-05-26 01:55:46 +02:00
#pragma region Measurements
2014-05-26 01:55:46 +02:00
static constexpr int32_t WH_SUMMARY = 309;
static constexpr int32_t WH_RESEARCH = 207;
static constexpr int32_t WH_OTHER_TABS = 257;
static constexpr int32_t WW_RESEARCH = 320;
static constexpr int32_t WW_OTHER_TABS = 530;
static constexpr int32_t RSH_SUMMARY = 266;
static constexpr int32_t RSH_RESEARCH = 164;
static constexpr int32_t RSH_OTHER_TABS = 214;
static constexpr int32_t RSW_RESEARCH = WW_RESEARCH;
static constexpr int32_t RSW_OTHER_TABS = WW_OTHER_TABS;
#pragma endregion
#pragma region Widgets
// clang-format off
#define MAIN_FINANCES_WIDGETS(TITLE, RSW, RSH, WW, WH) \
WINDOW_SHIM(TITLE, WW, WH), \
MakeWidget({0, 43}, {RSW, RSH}, WindowWidgetType::Resize, WindowColour::Secondary), \
MakeTab({ 3, 17}, STR_FINANCES_SHOW_SUMMARY_TAB_TIP ), \
MakeTab({ 34, 17}, STR_FINANCES_SHOW_CASH_TAB_TIP ), \
MakeTab({ 65, 17}, STR_FINANCES_SHOW_PARK_VALUE_TAB_TIP ), \
MakeTab({ 96, 17}, STR_FINANCES_SHOW_WEEKLY_PROFIT_TAB_TIP), \
MakeTab({127, 17}, STR_FINANCES_SHOW_MARKETING_TAB_TIP ), \
MakeTab({158, 17}, STR_FINANCES_RESEARCH_TIP )
2022-12-24 16:50:29 +01:00
static Widget _windowFinancesSummaryWidgets[] =
2018-01-04 04:43:34 +01:00
{
MAIN_FINANCES_WIDGETS(STR_FINANCIAL_SUMMARY, RSW_OTHER_TABS, RSH_SUMMARY, WW_OTHER_TABS, WH_SUMMARY),
MakeWidget ({130, 50}, {391, 211}, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_HORIZONTAL ),
MakeSpinnerWidgets({ 64, 279}, { 97, 14}, WindowWidgetType::Spinner, WindowColour::Secondary, STR_FINANCES_SUMMARY_LOAN_VALUE), // NB: 3 widgets.
kWidgetsEnd,
2014-05-26 01:55:46 +02:00
};
2022-12-24 16:50:29 +01:00
static Widget _windowFinancesCashWidgets[] =
2018-01-04 04:43:34 +01:00
{
MAIN_FINANCES_WIDGETS(STR_FINANCIAL_GRAPH, RSW_OTHER_TABS, RSH_OTHER_TABS, WW_OTHER_TABS, WH_OTHER_TABS),
kWidgetsEnd,
2014-05-26 01:55:46 +02:00
};
2022-12-24 16:50:29 +01:00
static Widget _windowFinancesParkValueWidgets[] =
2018-01-04 04:43:34 +01:00
{
MAIN_FINANCES_WIDGETS(STR_PARK_VALUE_GRAPH, RSW_OTHER_TABS, RSH_OTHER_TABS, WW_OTHER_TABS, WH_OTHER_TABS),
kWidgetsEnd,
2014-05-26 01:55:46 +02:00
};
2022-12-24 16:50:29 +01:00
static Widget _windowFinancesProfitWidgets[] =
2018-01-04 04:43:34 +01:00
{
MAIN_FINANCES_WIDGETS(STR_PROFIT_GRAPH, RSW_OTHER_TABS, RSH_OTHER_TABS, WW_OTHER_TABS, WH_OTHER_TABS),
kWidgetsEnd,
2014-05-26 01:55:46 +02:00
};
2022-12-24 16:50:29 +01:00
static Widget _windowFinancesMarketingWidgets[] =
2018-01-04 04:43:34 +01:00
{
MAIN_FINANCES_WIDGETS(STR_MARKETING, RSW_OTHER_TABS, RSH_OTHER_TABS, WW_OTHER_TABS, WH_OTHER_TABS),
MakeWidget({3, 47}, { WW_OTHER_TABS - 6, 45}, WindowWidgetType::Groupbox, WindowColour::Tertiary , STR_MARKETING_CAMPAIGNS_IN_OPERATION ),
MakeWidget({3, 47}, { WW_OTHER_TABS - 6, 206}, WindowWidgetType::Groupbox, WindowColour::Tertiary , STR_MARKETING_CAMPAIGNS_AVAILABLE ),
MakeWidget({8, 0}, {WW_OTHER_TABS - 16, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_START_THIS_MARKETING_CAMPAIGN),
MakeWidget({8, 0}, {WW_OTHER_TABS - 16, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_START_THIS_MARKETING_CAMPAIGN),
MakeWidget({8, 0}, {WW_OTHER_TABS - 16, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_START_THIS_MARKETING_CAMPAIGN),
MakeWidget({8, 0}, {WW_OTHER_TABS - 16, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_START_THIS_MARKETING_CAMPAIGN),
MakeWidget({8, 0}, {WW_OTHER_TABS - 16, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_START_THIS_MARKETING_CAMPAIGN),
MakeWidget({8, 0}, {WW_OTHER_TABS - 16, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_START_THIS_MARKETING_CAMPAIGN),
kWidgetsEnd,
2014-05-26 01:55:46 +02:00
};
2022-12-24 16:50:29 +01:00
static Widget _windowFinancesResearchWidgets[] =
2018-01-04 04:43:34 +01:00
{
MAIN_FINANCES_WIDGETS(STR_RESEARCH_FUNDING, RSW_RESEARCH, RSH_RESEARCH, WW_RESEARCH, WH_RESEARCH),
MakeWidget({ 3, 47}, { WW_RESEARCH - 6, 45}, WindowWidgetType::Groupbox, WindowColour::Tertiary, STR_RESEARCH_FUNDING_ ),
MakeWidget({ 8, 59}, { 160, 14}, WindowWidgetType::DropdownMenu, WindowColour::Tertiary, 0xFFFFFFFF, STR_SELECT_LEVEL_OF_RESEARCH_AND_DEVELOPMENT),
MakeWidget({156, 60}, { 11, 12}, WindowWidgetType::Button, WindowColour::Tertiary, STR_DROPDOWN_GLYPH, STR_SELECT_LEVEL_OF_RESEARCH_AND_DEVELOPMENT),
MakeWidget({ 3, 96}, { WW_RESEARCH - 6, 107}, WindowWidgetType::Groupbox, WindowColour::Tertiary, STR_RESEARCH_PRIORITIES ),
MakeWidget({ 8, 108}, {WW_RESEARCH - 14, 12}, WindowWidgetType::Checkbox, WindowColour::Tertiary, STR_RESEARCH_NEW_TRANSPORT_RIDES, STR_RESEARCH_NEW_TRANSPORT_RIDES_TIP ),
MakeWidget({ 8, 121}, {WW_RESEARCH - 14, 12}, WindowWidgetType::Checkbox, WindowColour::Tertiary, STR_RESEARCH_NEW_GENTLE_RIDES, STR_RESEARCH_NEW_GENTLE_RIDES_TIP ),
MakeWidget({ 8, 134}, {WW_RESEARCH - 14, 12}, WindowWidgetType::Checkbox, WindowColour::Tertiary, STR_RESEARCH_NEW_ROLLER_COASTERS, STR_RESEARCH_NEW_ROLLER_COASTERS_TIP ),
MakeWidget({ 8, 147}, {WW_RESEARCH - 14, 12}, WindowWidgetType::Checkbox, WindowColour::Tertiary, STR_RESEARCH_NEW_THRILL_RIDES, STR_RESEARCH_NEW_THRILL_RIDES_TIP ),
MakeWidget({ 8, 160}, {WW_RESEARCH - 14, 12}, WindowWidgetType::Checkbox, WindowColour::Tertiary, STR_RESEARCH_NEW_WATER_RIDES, STR_RESEARCH_NEW_WATER_RIDES_TIP ),
MakeWidget({ 8, 173}, {WW_RESEARCH - 14, 12}, WindowWidgetType::Checkbox, WindowColour::Tertiary, STR_RESEARCH_NEW_SHOPS_AND_STALLS, STR_RESEARCH_NEW_SHOPS_AND_STALLS_TIP ),
MakeWidget({ 8, 186}, {WW_RESEARCH - 14, 12}, WindowWidgetType::Checkbox, WindowColour::Tertiary, STR_RESEARCH_NEW_SCENERY_AND_THEMING, STR_RESEARCH_NEW_SCENERY_AND_THEMING_TIP ),
kWidgetsEnd,
2014-05-26 01:55:46 +02:00
};
// clang-format on
static Widget* _windowFinancesPageWidgets[] = {
_windowFinancesSummaryWidgets, // WINDOW_FINANCES_PAGE_SUMMARY
_windowFinancesCashWidgets, // WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH
_windowFinancesParkValueWidgets, // WINDOW_FINANCES_PAGE_VALUE_GRAPH
_windowFinancesProfitWidgets, // WINDOW_FINANCES_PAGE_PROFIT_GRAPH
_windowFinancesMarketingWidgets, // WINDOW_FINANCES_PAGE_MARKETING
_windowFinancesResearchWidgets, // WINDOW_FINANCES_PAGE_RESEARCH
};
static_assert(std::size(_windowFinancesPageWidgets) == WINDOW_FINANCES_PAGE_COUNT);
2014-05-26 01:55:46 +02:00
#pragma endregion
static constexpr StringId _windowFinancesSummaryRowLabels[EnumValue(ExpenditureType::Count)] = {
STR_FINANCES_SUMMARY_RIDE_CONSTRUCTION,
STR_FINANCES_SUMMARY_RIDE_RUNNING_COSTS,
STR_FINANCES_SUMMARY_LAND_PURCHASE,
STR_FINANCES_SUMMARY_LANDSCAPING,
STR_FINANCES_SUMMARY_PARK_ENTRANCE_TICKETS,
STR_FINANCES_SUMMARY_RIDE_TICKETS,
STR_FINANCES_SUMMARY_SHOP_SALES,
STR_FINANCES_SUMMARY_SHOP_STOCK,
STR_FINANCES_SUMMARY_FOOD_DRINK_SALES,
STR_FINANCES_SUMMARY_FOOD_DRINK_STOCK,
STR_FINANCES_SUMMARY_STAFF_WAGES,
STR_FINANCES_SUMMARY_MARKETING,
STR_FINANCES_SUMMARY_RESEARCH,
STR_FINANCES_SUMMARY_LOAN_INTEREST,
};
static constexpr int32_t _windowFinancesTabAnimationFrames[] = {
8, // WINDOW_FINANCES_PAGE_SUMMARY
16, // WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH
16, // WINDOW_FINANCES_PAGE_VALUE_GRAPH
16, // WINDOW_FINANCES_PAGE_PROFIT_GRAPH
19, // WINDOW_FINANCES_PAGE_MARKETING
8, // WINDOW_FINANCES_PAGE_RESEARCH
};
static_assert(std::size(_windowFinancesTabAnimationFrames) == WINDOW_FINANCES_PAGE_COUNT);
static constexpr int32_t EXPENDITURE_COLUMN_WIDTH = 80;
static constexpr uint32_t _windowFinancesPageHoldDownWidgets[] = {
(1uLL << WIDX_LOAN_INCREASE) | (1uLL << WIDX_LOAN_DECREASE), // WINDOW_FINANCES_PAGE_SUMMARY
0, // WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH
0, // WINDOW_FINANCES_PAGE_VALUE_GRAPH
0, // WINDOW_FINANCES_PAGE_PROFIT_GRAPH
0, // WINDOW_FINANCES_PAGE_MARKETING
0, // WINDOW_FINANCES_PAGE_RESEARCH
};
static_assert(std::size(_windowFinancesPageHoldDownWidgets) == WINDOW_FINANCES_PAGE_COUNT);
2017-09-05 20:55:18 +02:00
#pragma endregion
class FinancesWindow final : public Window
{
private:
uint32_t _lastPaintedMonth;
2014-05-26 02:29:59 +02:00
void SetDisabledTabs()
{
disabled_widgets = (GetGameState().Park.Flags & PARK_FLAGS_FORBID_MARKETING_CAMPAIGN) ? (1uLL << WIDX_TAB_5) : 0;
}
public:
void OnOpen() override
{
SetPage(WINDOW_FINANCES_PAGE_SUMMARY);
_lastPaintedMonth = std::numeric_limits<uint32_t>::max();
ResearchUpdateUncompletedTypes();
}
2014-05-26 02:29:59 +02:00
void OnUpdate() override
{
frame_no++;
InvalidateWidget(WIDX_TAB_1 + page);
}
void OnMouseDown(WidgetIndex widgetIndex) override
{
switch (page)
{
case WINDOW_FINANCES_PAGE_SUMMARY:
OnMouseDownSummary(widgetIndex);
break;
case WINDOW_FINANCES_PAGE_RESEARCH:
WindowResearchFundingMouseDown(this, widgetIndex, WIDX_RESEARCH_FUNDING);
break;
}
}
void OnMouseUp(WidgetIndex widgetIndex) override
{
switch (widgetIndex)
{
case WIDX_CLOSE:
Close();
break;
case WIDX_TAB_1:
case WIDX_TAB_2:
case WIDX_TAB_3:
case WIDX_TAB_4:
case WIDX_TAB_5:
case WIDX_TAB_6:
SetPage(widgetIndex - WIDX_TAB_1);
break;
default:
switch (page)
{
case WINDOW_FINANCES_PAGE_MARKETING:
OnMouseUpMarketing(widgetIndex);
break;
case WINDOW_FINANCES_PAGE_RESEARCH:
WindowResearchFundingMouseUp(widgetIndex, WIDX_RESEARCH_FUNDING);
}
break;
}
}
void OnDropdown(WidgetIndex widgetIndex, int32_t selectedIndex) override
{
if (page == WINDOW_FINANCES_PAGE_RESEARCH)
{
WindowResearchFundingDropdown(widgetIndex, selectedIndex, WIDX_RESEARCH_FUNDING);
}
}
void OnPrepareDraw() override
{
auto* targetWidgets = _windowFinancesPageWidgets[page];
if (widgets != targetWidgets)
{
widgets = targetWidgets;
WindowInitScrollWidgets(*this);
}
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_6);
for (auto i = 0; i < WINDOW_FINANCES_PAGE_COUNT; i++)
SetWidgetPressed(WIDX_TAB_1 + i, false);
SetWidgetPressed(WIDX_TAB_1 + page, true);
switch (page)
{
case WINDOW_FINANCES_PAGE_SUMMARY:
OnPrepareDrawSummary();
break;
case WINDOW_FINANCES_PAGE_MARKETING:
OnPrepareDrawMarketing();
break;
case WINDOW_FINANCES_PAGE_RESEARCH:
WindowResearchFundingPrepareDraw(this, WIDX_RESEARCH_FUNDING);
break;
}
}
void OnDraw(DrawPixelInfo& dpi) override
{
DrawWidgets(dpi);
DrawTabImages(dpi);
switch (page)
{
case WINDOW_FINANCES_PAGE_SUMMARY:
OnDrawSummary(dpi);
break;
case WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH:
OnDrawFinancialGraph(dpi);
break;
case WINDOW_FINANCES_PAGE_VALUE_GRAPH:
OnDrawParkValueGraph(dpi);
break;
case WINDOW_FINANCES_PAGE_PROFIT_GRAPH:
OnDrawProfitGraph(dpi);
break;
case WINDOW_FINANCES_PAGE_MARKETING:
OnDrawMarketing(dpi);
break;
case WINDOW_FINANCES_PAGE_RESEARCH:
WindowResearchFundingDraw(this, dpi);
break;
}
}
ScreenSize OnScrollGetSize(int32_t scrollIndex) override
{
if (page == WINDOW_FINANCES_PAGE_SUMMARY)
{
return { EXPENDITURE_COLUMN_WIDTH * (SummaryMaxAvailableMonth() + 1), 0 };
}
return {};
}
void OnScrollDraw(int32_t scrollIndex, DrawPixelInfo& dpi) override
{
if (page != WINDOW_FINANCES_PAGE_SUMMARY)
return;
auto screenCoords = ScreenCoordsXY{ 0, TABLE_CELL_HEIGHT + 2 };
Widget self = widgets[WIDX_SUMMARY_SCROLL];
int32_t row_width = std::max<uint16_t>(scrolls[0].h_right, self.width());
// Expenditure / Income row labels
for (int32_t i = 0; i < static_cast<int32_t>(ExpenditureType::Count); i++)
{
// Darken every even row
if (i % 2 == 0)
GfxFillRect(
dpi,
{ screenCoords - ScreenCoordsXY{ 0, 1 },
screenCoords + ScreenCoordsXY{ row_width, (TABLE_CELL_HEIGHT - 2) } },
ColourMapA[colours[1]].lighter | 0x1000000);
screenCoords.y += TABLE_CELL_HEIGHT;
}
auto& gameState = GetGameState();
// Expenditure / Income values for each month
auto currentMonthYear = GetDate().GetMonthsElapsed();
for (int32_t i = SummaryMaxAvailableMonth(); i >= 0; i--)
{
screenCoords.y = 0;
uint16_t monthyear = currentMonthYear - i;
// Month heading
auto ft = Formatter();
ft.Add<StringId>(STR_FINANCES_SUMMARY_MONTH_HEADING);
ft.Add<uint16_t>(monthyear);
DrawTextBasic(
dpi, screenCoords + ScreenCoordsXY{ EXPENDITURE_COLUMN_WIDTH, 0 },
monthyear == currentMonthYear ? STR_WINDOW_COLOUR_2_STRINGID : STR_BLACK_STRING, ft,
{ TextUnderline::On, TextAlignment::RIGHT });
screenCoords.y += 14;
// Month expenditures
money64 profit = 0;
for (int32_t j = 0; j < static_cast<int32_t>(ExpenditureType::Count); j++)
{
auto expenditure = gameState.ExpenditureTable[i][j];
if (expenditure != 0)
{
profit += expenditure;
const StringId format = expenditure >= 0 ? STR_FINANCES_SUMMARY_INCOME_VALUE
: STR_FINANCES_SUMMARY_EXPENDITURE_VALUE;
ft = Formatter();
ft.Add<money64>(expenditure);
DrawTextBasic(
dpi, screenCoords + ScreenCoordsXY{ EXPENDITURE_COLUMN_WIDTH, 0 }, format, ft,
{ TextAlignment::RIGHT });
}
screenCoords.y += TABLE_CELL_HEIGHT;
}
screenCoords.y += 4;
// Month profit
const StringId format = profit >= 0 ? STR_FINANCES_SUMMARY_INCOME_VALUE : STR_FINANCES_SUMMARY_LOSS_VALUE;
ft = Formatter();
ft.Add<money64>(profit);
DrawTextBasic(
dpi, screenCoords + ScreenCoordsXY{ EXPENDITURE_COLUMN_WIDTH, 0 }, format, ft, { TextAlignment::RIGHT });
GfxFillRect(
dpi,
{ screenCoords + ScreenCoordsXY{ 10, -2 }, screenCoords + ScreenCoordsXY{ EXPENDITURE_COLUMN_WIDTH, -2 } },
PALETTE_INDEX_10);
2014-08-09 15:47:58 +02:00
screenCoords.x += EXPENDITURE_COLUMN_WIDTH;
}
_lastPaintedMonth = currentMonthYear;
}
void SetPage(int32_t p)
{
page = p;
frame_no = 0;
hold_down_widgets = _windowFinancesPageHoldDownWidgets[p];
pressed_widgets = 0;
widgets = _windowFinancesPageWidgets[p];
SetDisabledTabs();
Invalidate();
if (p == WINDOW_FINANCES_PAGE_RESEARCH)
{
width = WW_RESEARCH;
height = WH_RESEARCH;
}
else if (p == WINDOW_FINANCES_PAGE_SUMMARY)
{
width = WW_OTHER_TABS;
height = WH_SUMMARY;
}
else
{
width = WW_OTHER_TABS;
height = WH_OTHER_TABS;
}
OnResize();
OnPrepareDraw();
WindowInitScrollWidgets(*this);
// Scroll summary all the way to the right, initially.
if (p == WINDOW_FINANCES_PAGE_SUMMARY)
InitialiseScrollPosition(WIDX_SUMMARY_SCROLL, 0);
Invalidate();
}
#pragma region Summary Events
void OnMouseDownSummary(WidgetIndex widgetIndex)
2018-06-22 23:21:44 +02:00
{
auto& gameState = GetGameState();
switch (widgetIndex)
{
case WIDX_LOAN_INCREASE:
{
// If loan can be increased, do so.
// If not, action shows error message.
auto newLoan = gameState.BankLoan + 1000.00_GBP;
if (gameState.BankLoan < gameState.MaxBankLoan)
{
newLoan = std::min(gameState.MaxBankLoan, newLoan);
}
auto gameAction = ParkSetLoanAction(newLoan);
GameActions::Execute(&gameAction);
break;
}
case WIDX_LOAN_DECREASE:
{
// If loan is positive, decrease it.
// If loan is negative, action shows error message.
// If loan is exactly 0, prevent error message.
if (gameState.BankLoan != 0)
{
auto newLoan = gameState.BankLoan - 1000.00_GBP;
if (gameState.BankLoan > 0)
{
newLoan = std::max(static_cast<money64>(0LL), newLoan);
}
auto gameAction = ParkSetLoanAction(newLoan);
GameActions::Execute(&gameAction);
}
break;
}
}
}
void OnPrepareDrawSummary()
{
// Setting loan widget's format arguments here.
// Nothing else should use the global formatter until
// drawing has completed.
auto ft = Formatter::Common();
ft.Increment(6);
ft.Add<money64>(GetGameState().BankLoan);
// Keep up with new months being added in the first two years.
if (GetDate().GetMonthsElapsed() != _lastPaintedMonth)
InitialiseScrollPosition(WIDX_SUMMARY_SCROLL, 0);
}
void OnDrawSummary(DrawPixelInfo& dpi)
{
auto screenCoords = windowPos + ScreenCoordsXY{ 8, 51 };
auto& gameState = GetGameState();
// Expenditure / Income heading
DrawTextBasic(
dpi, screenCoords, STR_FINANCES_SUMMARY_EXPENDITURE_INCOME, {},
{ COLOUR_BLACK, TextUnderline::On, TextAlignment::LEFT });
screenCoords.y += 14;
// Expenditure / Income row labels
for (int32_t i = 0; i < static_cast<int32_t>(ExpenditureType::Count); i++)
{
// Darken every even row
if (i % 2 == 0)
GfxFillRect(
dpi,
{ screenCoords - ScreenCoordsXY{ 0, 1 },
screenCoords + ScreenCoordsXY{ 121, (TABLE_CELL_HEIGHT - 2) } },
ColourMapA[colours[1]].lighter | 0x1000000);
DrawTextBasic(dpi, screenCoords - ScreenCoordsXY{ 0, 1 }, _windowFinancesSummaryRowLabels[i]);
screenCoords.y += TABLE_CELL_HEIGHT;
}
// Horizontal rule below expenditure / income table
GfxFillRectInset(
dpi, { windowPos + ScreenCoordsXY{ 8, 272 }, windowPos + ScreenCoordsXY{ 8 + 513, 272 + 1 } }, colours[1],
INSET_RECT_FLAG_BORDER_INSET);
// Loan and interest rate
DrawTextBasic(dpi, windowPos + ScreenCoordsXY{ 8, 279 }, STR_FINANCES_SUMMARY_LOAN);
if (!(gameState.Park.Flags & PARK_FLAGS_RCT1_INTEREST))
{
auto ft = Formatter();
ft.Add<uint16_t>(gameState.BankLoanInterestRate);
DrawTextBasic(dpi, windowPos + ScreenCoordsXY{ 167, 279 }, STR_FINANCES_SUMMARY_AT_X_PER_YEAR, ft);
}
// Current cash
auto ft = Formatter();
ft.Add<money64>(gameState.Cash);
StringId stringId = gameState.Cash >= 0 ? STR_CASH_LABEL : STR_CASH_NEGATIVE_LABEL;
DrawTextBasic(dpi, windowPos + ScreenCoordsXY{ 8, 294 }, stringId, ft);
// Objective related financial information
if (gameState.ScenarioObjective.Type == OBJECTIVE_MONTHLY_FOOD_INCOME)
{
auto lastMonthProfit = FinanceGetLastMonthShopProfit();
ft = Formatter();
ft.Add<money64>(lastMonthProfit);
DrawTextBasic(
dpi, windowPos + ScreenCoordsXY{ 280, 279 }, STR_LAST_MONTH_PROFIT_FROM_FOOD_DRINK_MERCHANDISE_SALES_LABEL,
ft);
}
else
{
// Park value and company value
ft = Formatter();
ft.Add<money64>(gameState.Park.Value);
DrawTextBasic(dpi, windowPos + ScreenCoordsXY{ 280, 279 }, STR_PARK_VALUE_LABEL, ft);
ft = Formatter();
ft.Add<money64>(gameState.CompanyValue);
DrawTextBasic(dpi, windowPos + ScreenCoordsXY{ 280, 294 }, STR_COMPANY_VALUE_LABEL, ft);
}
}
uint16_t SummaryMaxAvailableMonth()
{
return std::min<uint16_t>(GetDate().GetMonthsElapsed(), EXPENDITURE_TABLE_MONTH_COUNT - 1);
}
#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();
2024-01-25 12:13:14 +01:00
// 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)
{
if (widgetIndex >= WIDX_CAMPAIGN_1 && widgetIndex <= WIDX_CAMPAIGN_6)
{
ContextOpenDetailWindow(WD_NEW_CAMPAIGN, widgetIndex - WIDX_CAMPAIGN_1);
}
}
void OnPrepareDrawMarketing()
{
// Count number of active campaigns
int32_t numActiveCampaigns = static_cast<int32_t>(gMarketingCampaigns.size());
int32_t y = std::max(1, numActiveCampaigns) * LIST_ROW_HEIGHT + 92;
// Update group box positions
_windowFinancesMarketingWidgets[WIDX_ACTIVE_CAMPAIGNS_GROUP].bottom = y - 22;
_windowFinancesMarketingWidgets[WIDX_CAMPAIGNS_AVAILABLE_GROUP].top = y - 13;
// Update new campaign button visibility
y += 3;
for (int32_t i = 0; i < ADVERTISING_CAMPAIGN_COUNT; i++)
{
auto campaignButton = &_windowFinancesMarketingWidgets[WIDX_CAMPAIGN_1 + i];
auto marketingCampaign = MarketingGetCampaign(i);
if (marketingCampaign == nullptr && MarketingIsCampaignTypeApplicable(i))
{
campaignButton->type = WindowWidgetType::Button;
campaignButton->top = y;
campaignButton->bottom = y + BUTTON_FACE_HEIGHT + 1;
y += BUTTON_FACE_HEIGHT + 2;
}
else
{
campaignButton->type = WindowWidgetType::Empty;
}
}
}
void OnDrawMarketing(DrawPixelInfo& dpi)
{
auto screenCoords = windowPos + ScreenCoordsXY{ 8, 62 };
int32_t noCampaignsActive = 1;
for (int32_t i = 0; i < ADVERTISING_CAMPAIGN_COUNT; i++)
{
auto marketingCampaign = MarketingGetCampaign(i);
if (marketingCampaign == nullptr)
continue;
noCampaignsActive = 0;
auto ft = Formatter();
// Set special parameters
switch (i)
{
case ADVERTISING_CAMPAIGN_RIDE_FREE:
case ADVERTISING_CAMPAIGN_RIDE:
{
auto campaignRide = GetRide(marketingCampaign->RideId);
if (campaignRide != nullptr)
{
campaignRide->FormatNameTo(ft);
}
else
{
ft.Add<StringId>(STR_NONE);
}
break;
}
case ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE:
ft.Add<StringId>(GetShopItemDescriptor(marketingCampaign->ShopItemType).Naming.Plural);
break;
default:
{
2024-03-26 19:01:50 +01:00
auto parkName = OpenRCT2::GetGameState().Park.Name.c_str();
ft.Add<StringId>(STR_STRING);
ft.Add<const char*>(parkName);
}
}
// Advertisement
DrawTextEllipsised(dpi, screenCoords + ScreenCoordsXY{ 4, 0 }, 296, MarketingCampaignNames[i][1], ft);
// Duration
uint16_t weeksRemaining = marketingCampaign->WeeksLeft;
ft = Formatter();
ft.Add<uint16_t>(weeksRemaining);
DrawTextBasic(
dpi, screenCoords + ScreenCoordsXY{ 304, 0 },
weeksRemaining == 1 ? STR_1_WEEK_REMAINING : STR_X_WEEKS_REMAINING, ft);
screenCoords.y += LIST_ROW_HEIGHT;
}
if (noCampaignsActive)
{
DrawTextBasic(dpi, screenCoords + ScreenCoordsXY{ 4, 0 }, STR_MARKETING_CAMPAIGNS_NONE);
screenCoords.y += LIST_ROW_HEIGHT;
}
screenCoords.y += 34;
// Draw campaign button text
for (int32_t i = 0; i < ADVERTISING_CAMPAIGN_COUNT; i++)
{
auto campaignButton = &_windowFinancesMarketingWidgets[WIDX_CAMPAIGN_1 + i];
if (campaignButton->type != WindowWidgetType::Empty)
{
// Draw button text
DrawTextBasic(dpi, screenCoords + ScreenCoordsXY{ 4, 0 }, MarketingCampaignNames[i][0]);
auto ft = Formatter();
ft.Add<money64>(AdvertisingCampaignPricePerWeek[i]);
DrawTextBasic(dpi, screenCoords + ScreenCoordsXY{ WH_SUMMARY, 0 }, STR_MARKETING_PER_WEEK, ft);
screenCoords.y += BUTTON_FACE_HEIGHT + 2;
}
}
}
#pragma endregion
void InitialiseScrollPosition(WidgetIndex widgetIndex, int32_t scrollId)
{
const auto& widget = this->widgets[widgetIndex];
scrolls[scrollId].h_left = std::max(0, scrolls[scrollId].h_right - (widget.width() - 2));
WidgetScrollUpdateThumbs(*this, widgetIndex);
}
void DrawTabImage(DrawPixelInfo& dpi, int32_t tabPage, int32_t spriteIndex)
{
WidgetIndex widgetIndex = WIDX_TAB_1 + tabPage;
if (!IsWidgetDisabled(widgetIndex))
{
if (this->page == tabPage)
{
int32_t frame = frame_no / 2;
spriteIndex += (frame % _windowFinancesTabAnimationFrames[this->page]);
}
GfxDrawSprite(
dpi, ImageId(spriteIndex),
windowPos + ScreenCoordsXY{ widgets[widgetIndex].left, widgets[widgetIndex].top });
}
}
void DrawTabImages(DrawPixelInfo& dpi)
{
DrawTabImage(dpi, WINDOW_FINANCES_PAGE_SUMMARY, SPR_TAB_FINANCES_SUMMARY_0);
DrawTabImage(dpi, WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH, SPR_TAB_FINANCES_FINANCIAL_GRAPH_0);
DrawTabImage(dpi, WINDOW_FINANCES_PAGE_VALUE_GRAPH, SPR_TAB_FINANCES_VALUE_GRAPH_0);
DrawTabImage(dpi, WINDOW_FINANCES_PAGE_PROFIT_GRAPH, SPR_TAB_FINANCES_PROFIT_GRAPH_0);
DrawTabImage(dpi, WINDOW_FINANCES_PAGE_MARKETING, SPR_TAB_FINANCES_MARKETING_0);
DrawTabImage(dpi, WINDOW_FINANCES_PAGE_RESEARCH, SPR_TAB_FINANCES_RESEARCH_0);
}
2014-05-26 02:29:59 +02:00
void OnResize() override
{
ResizeFrameWithPage();
}
};
static FinancesWindow* FinancesWindowOpen(uint8_t page)
2018-06-22 23:21:44 +02:00
{
auto* window = WindowFocusOrCreate<FinancesWindow>(WindowClass::Finances, WW_OTHER_TABS, WH_SUMMARY, WF_10);
if (window != nullptr && page != WINDOW_FINANCES_PAGE_SUMMARY)
window->SetPage(page);
return window;
}
WindowBase* FinancesOpen()
{
return WindowFocusOrCreate<FinancesWindow>(WindowClass::Finances, WW_OTHER_TABS, WH_SUMMARY, WF_10);
}
WindowBase* FinancesResearchOpen()
{
return FinancesWindowOpen(WINDOW_FINANCES_PAGE_RESEARCH);
}
WindowBase* FinancesMarketingOpen()
{
return FinancesWindowOpen(WINDOW_FINANCES_PAGE_MARKETING);
}
} // namespace OpenRCT2::Ui::Windows