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

1352 lines
46 KiB
C++

/*****************************************************************************
* 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 "../interface/Theme.h"
#include <algorithm>
#include <array>
#include <limits>
#include <openrct2-ui/interface/Dropdown.h>
#include <openrct2-ui/interface/Graph.h>
#include <openrct2-ui/interface/LandTool.h>
#include <openrct2-ui/interface/Objective.h>
#include <openrct2-ui/interface/Viewport.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>
#include <openrct2/Input.h>
#include <openrct2/actions/ParkSetEntranceFeeAction.h>
#include <openrct2/actions/ParkSetNameAction.h>
#include <openrct2/config/Config.h>
#include <openrct2/localisation/Date.h>
#include <openrct2/localisation/Formatter.h>
#include <openrct2/localisation/Localisation.h>
#include <openrct2/management/Award.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
{
static constexpr StringId WINDOW_TITLE = STR_STRINGID;
static constexpr int32_t WH = 224;
// clang-format off
enum WindowParkPage {
WINDOW_PARK_PAGE_ENTRANCE,
WINDOW_PARK_PAGE_RATING,
WINDOW_PARK_PAGE_GUESTS,
WINDOW_PARK_PAGE_PRICE,
WINDOW_PARK_PAGE_STATS,
WINDOW_PARK_PAGE_OBJECTIVE,
WINDOW_PARK_PAGE_AWARDS,
WINDOW_PARK_PAGE_COUNT,
};
enum WindowParkWidgetIdx {
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_7,
WIDX_VIEWPORT = 11,
WIDX_STATUS,
WIDX_OPEN_OR_CLOSE,
WIDX_BUY_LAND_RIGHTS,
WIDX_LOCATE,
WIDX_RENAME,
WIDX_CLOSE_LIGHT,
WIDX_OPEN_LIGHT,
WIDX_PRICE_LABEL = 11,
WIDX_PRICE,
WIDX_INCREASE_PRICE,
WIDX_DECREASE_PRICE,
WIDX_ENTER_NAME = 11
};
#pragma region Widgets
#define MAIN_PARK_WIDGETS(WW) \
WINDOW_SHIM(WINDOW_TITLE, WW, WH), \
MakeWidget({ 0, 43}, {WW, 131}, WindowWidgetType::Resize, WindowColour::Secondary), /* tab content panel */ \
MakeTab ({ 3, 17}, STR_PARK_ENTRANCE_TAB_TIP ), /* tab 1 */ \
MakeTab ({ 34, 17}, STR_PARK_RATING_TAB_TIP ), /* tab 2 */ \
MakeTab ({ 65, 17}, STR_PARK_GUESTS_TAB_TIP ), /* tab 3 */ \
MakeTab ({ 96, 17}, STR_PARK_PRICE_TAB_TIP ), /* tab 4 */ \
MakeTab ({127, 17}, STR_PARK_STATS_TAB_TIP ), /* tab 5 */ \
MakeTab ({158, 17}, STR_PARK_OBJECTIVE_TAB_TIP ), /* tab 6 */ \
MakeTab ({189, 17}, STR_PARK_AWARDS_TAB_TIP ) /* tab 7 */
static Widget _entranceWidgets[] = {
MAIN_PARK_WIDGETS(230),
MakeWidget({ 3, 46}, {202, 115}, WindowWidgetType::Viewport, WindowColour::Secondary ), // viewport
MakeWidget({ 3, 161}, {202, 11}, WindowWidgetType::LabelCentred, WindowColour::Secondary ), // status
MakeWidget({205, 49}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_OPEN_OR_CLOSE_PARK_TIP ), // open / close
MakeWidget({205, 73}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_BUY_LAND_RIGHTS), STR_BUY_LAND_AND_CONSTRUCTION_RIGHTS_TIP), // buy land rights
MakeWidget({205, 97}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_LOCATE), STR_LOCATE_SUBJECT_TIP ), // locate
MakeWidget({205, 121}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_RENAME), STR_NAME_PARK_TIP ), // rename
MakeWidget({210, 51}, { 14, 15}, WindowWidgetType::ImgBtn, WindowColour::Secondary, ImageId(SPR_G2_RCT1_CLOSE_BUTTON_0), STR_CLOSE_PARK_TIP ),
MakeWidget({210, 66}, { 14, 14}, WindowWidgetType::ImgBtn, WindowColour::Secondary, ImageId(SPR_G2_RCT1_OPEN_BUTTON_0), STR_OPEN_PARK_TIP ),
kWidgetsEnd,
};
static Widget _ratingWidgets[] = {
MAIN_PARK_WIDGETS(255),
kWidgetsEnd,
};
static Widget _guestsWidgets[] = {
MAIN_PARK_WIDGETS(255),
kWidgetsEnd,
};
static Widget _priceWidgets[] = {
MAIN_PARK_WIDGETS(230),
MakeWidget ({ 21, 50}, {126, 14}, WindowWidgetType::Label, WindowColour::Secondary, STR_ADMISSION_PRICE),
MakeSpinnerWidgets({147, 50}, { 76, 14}, WindowWidgetType::Spinner, WindowColour::Secondary ), // Price (3 widgets)
kWidgetsEnd,
};
static Widget _statsWidgets[] = {
MAIN_PARK_WIDGETS(230),
kWidgetsEnd,
};
static Widget _objectiveWidgets[] = {
MAIN_PARK_WIDGETS(230),
MakeWidget({7, 207}, {216, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_ENTER_NAME_INTO_SCENARIO_CHART), // enter name
kWidgetsEnd,
};
static Widget _awardsWidgets[] = {
MAIN_PARK_WIDGETS(230),
kWidgetsEnd,
};
static std::array<Widget*, WINDOW_PARK_PAGE_COUNT> _pagedWidgets = {
_entranceWidgets,
_ratingWidgets,
_guestsWidgets,
_priceWidgets,
_statsWidgets,
_objectiveWidgets,
_awardsWidgets,
};
#pragma endregion
static std::array<uint32_t, WINDOW_PARK_PAGE_COUNT> _pagedHoldDownWidgets = {
0,
0,
0,
(1uLL << WIDX_INCREASE_PRICE) |
(1uLL << WIDX_DECREASE_PRICE),
0,
0,
0,
};
struct WindowParkAward {
StringId text;
uint32_t sprite;
};
static constexpr WindowParkAward _parkAwards[] = {
{ STR_AWARD_MOST_UNTIDY, SPR_AWARD_MOST_UNTIDY },
{ STR_AWARD_MOST_TIDY, SPR_AWARD_MOST_TIDY },
{ STR_AWARD_BEST_ROLLERCOASTERS, SPR_AWARD_BEST_ROLLERCOASTERS },
{ STR_AWARD_BEST_VALUE, SPR_AWARD_BEST_VALUE },
{ STR_AWARD_MOST_BEAUTIFUL, SPR_AWARD_MOST_BEAUTIFUL },
{ STR_AWARD_WORST_VALUE, SPR_AWARD_WORST_VALUE },
{ STR_AWARD_SAFEST, SPR_AWARD_SAFEST },
{ STR_AWARD_BEST_STAFF, SPR_AWARD_BEST_STAFF },
{ STR_AWARD_BEST_FOOD, SPR_AWARD_BEST_FOOD },
{ STR_AWARD_WORST_FOOD, SPR_AWARD_WORST_FOOD },
{ STR_AWARD_BEST_TOILETS, SPR_AWARD_BEST_TOILETS },
{ STR_AWARD_MOST_DISAPPOINTING, SPR_AWARD_MOST_DISAPPOINTING },
{ STR_AWARD_BEST_WATER_RIDES, SPR_AWARD_BEST_WATER_RIDES },
{ STR_AWARD_BEST_CUSTOM_DESIGNED_RIDES, SPR_AWARD_BEST_CUSTOM_DESIGNED_RIDES },
{ STR_AWARD_MOST_DAZZLING_RIDE_COLOURS, SPR_AWARD_MOST_DAZZLING_RIDE_COLOURS },
{ STR_AWARD_MOST_CONFUSING_LAYOUT, SPR_AWARD_MOST_CONFUSING_LAYOUT },
{ STR_AWARD_BEST_GENTLE_RIDES, SPR_AWARD_BEST_GENTLE_RIDES },
};
// clang-format on
class ParkWindow final : public Window
{
int32_t _numberOfStaff = -1;
int32_t _numberOfRides = -1;
uint8_t _peepAnimationFrame = 0;
public:
void OnOpen() override
{
number = 0;
frame_no = 0;
_numberOfRides = -1;
_numberOfStaff = -1;
_peepAnimationFrame = 0;
SetPage(0);
}
void OnClose() override
{
if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE) && classification == gCurrentToolWidget.window_classification
&& number == gCurrentToolWidget.window_number)
{
ToolCancel();
}
}
void OnMouseUp(WidgetIndex idx) override
{
switch (idx)
{
case WIDX_CLOSE:
Close();
return;
case WIDX_TAB_1:
case WIDX_TAB_2:
case WIDX_TAB_3:
case WIDX_TAB_4:
case WIDX_TAB_5:
case WIDX_TAB_6:
case WIDX_TAB_7:
SetPage(idx - WIDX_TAB_1);
return;
}
switch (page)
{
case WINDOW_PARK_PAGE_ENTRANCE:
OnMouseUpEntrance(idx);
break;
case WINDOW_PARK_PAGE_OBJECTIVE:
OnMouseUpObjective(idx);
break;
}
}
void OnResize() override
{
switch (page)
{
case WINDOW_PARK_PAGE_ENTRANCE:
OnResizeEntrance();
break;
case WINDOW_PARK_PAGE_RATING:
OnResizeRating();
break;
case WINDOW_PARK_PAGE_GUESTS:
OnResizeGuests();
break;
case WINDOW_PARK_PAGE_PRICE:
OnResizePrice();
break;
case WINDOW_PARK_PAGE_STATS:
OnResizeStats();
break;
case WINDOW_PARK_PAGE_OBJECTIVE:
OnResizeObjective();
break;
case WINDOW_PARK_PAGE_AWARDS:
OnResizeAwards();
break;
}
}
void OnMouseDown(WidgetIndex idx) override
{
switch (page)
{
case WINDOW_PARK_PAGE_ENTRANCE:
OnMouseDownEntrance(idx);
break;
case WINDOW_PARK_PAGE_PRICE:
OnMouseDownPrice(idx);
break;
}
}
void OnDropdown(WidgetIndex widgetIndex, int32_t selectedIndex) override
{
switch (page)
{
case WINDOW_PARK_PAGE_ENTRANCE:
OnDropdownEntrance(widgetIndex, selectedIndex);
break;
}
}
void OnUpdate() override
{
switch (page)
{
case WINDOW_PARK_PAGE_ENTRANCE:
OnUpdateEntrance();
break;
case WINDOW_PARK_PAGE_RATING:
OnUpdateRating();
break;
case WINDOW_PARK_PAGE_GUESTS:
OnUpdateGuests();
break;
case WINDOW_PARK_PAGE_PRICE:
OnUpdatePrice();
break;
case WINDOW_PARK_PAGE_STATS:
OnUpdateStats();
break;
case WINDOW_PARK_PAGE_OBJECTIVE:
OnUpdateObjective();
break;
case WINDOW_PARK_PAGE_AWARDS:
OnUpdateAwards();
break;
}
}
void OnTextInput(WidgetIndex widgetIndex, std::string_view text) override
{
switch (page)
{
case WINDOW_PARK_PAGE_ENTRANCE:
OnTextInputEntrance(widgetIndex, text);
break;
case WINDOW_PARK_PAGE_OBJECTIVE:
OnTextInputObjective(widgetIndex, text);
break;
}
}
void OnPrepareDraw() override
{
switch (page)
{
case WINDOW_PARK_PAGE_ENTRANCE:
OnPrepareDrawEntrance();
break;
case WINDOW_PARK_PAGE_RATING:
OnPrepareDrawRating();
break;
case WINDOW_PARK_PAGE_GUESTS:
OnPrepareDrawGuests();
break;
case WINDOW_PARK_PAGE_PRICE:
OnPrepareDrawPrice();
break;
case WINDOW_PARK_PAGE_STATS:
OnPrepareDrawStats();
break;
case WINDOW_PARK_PAGE_OBJECTIVE:
OnPrepareDrawObjective();
break;
case WINDOW_PARK_PAGE_AWARDS:
OnPrepareDrawAwards();
break;
}
}
void OnDraw(DrawPixelInfo& dpi) override
{
switch (page)
{
case WINDOW_PARK_PAGE_ENTRANCE:
OnDrawEntrance(dpi);
break;
case WINDOW_PARK_PAGE_RATING:
OnDrawRating(dpi);
break;
case WINDOW_PARK_PAGE_GUESTS:
OnDrawGuests(dpi);
break;
case WINDOW_PARK_PAGE_PRICE:
OnDrawPrice(dpi);
break;
case WINDOW_PARK_PAGE_STATS:
OnDrawStats(dpi);
break;
case WINDOW_PARK_PAGE_OBJECTIVE:
OnDrawObjective(dpi);
break;
case WINDOW_PARK_PAGE_AWARDS:
OnDrawAwards(dpi);
break;
}
}
private:
void SetDisabledTabs()
{
// Disable price tab if money is disabled
disabled_widgets = (GetGameState().Park.Flags & PARK_FLAGS_NO_MONEY) ? (1uLL << WIDX_TAB_4) : 0;
}
void PrepareWindowTitleText()
{
auto parkName = OpenRCT2::GetGameState().Park.Name.c_str();
auto ft = Formatter::Common();
ft.Add<StringId>(STR_STRING);
ft.Add<const char*>(parkName);
}
#pragma region Entrance page
void OnMouseUpEntrance(WidgetIndex widgetIndex)
{
switch (widgetIndex)
{
case WIDX_BUY_LAND_RIGHTS:
ContextOpenWindow(WindowClass::LandRights);
break;
case WIDX_LOCATE:
ScrollToViewport();
break;
case WIDX_RENAME:
{
auto& park = OpenRCT2::GetGameState().Park;
WindowTextInputRawOpen(
this, WIDX_RENAME, STR_PARK_NAME, STR_ENTER_PARK_NAME, {}, park.Name.c_str(), USER_STRING_MAX_LENGTH);
break;
}
case WIDX_CLOSE_LIGHT:
Park::SetOpen(false);
break;
case WIDX_OPEN_LIGHT:
Park::SetOpen(true);
break;
}
}
void OnResizeEntrance()
{
flags |= WF_RESIZABLE;
WindowSetResize(*this, 230, 174 + 9, 230 * 3, (274 + 9) * 3);
InitViewport();
}
void OnMouseDownEntrance(WidgetIndex widgetIndex)
{
if (widgetIndex == WIDX_OPEN_OR_CLOSE)
{
auto& widget = widgets[widgetIndex];
gDropdownItems[0].Format = STR_DROPDOWN_MENU_LABEL;
gDropdownItems[1].Format = STR_DROPDOWN_MENU_LABEL;
gDropdownItems[0].Args = STR_CLOSE_PARK;
gDropdownItems[1].Args = STR_OPEN_PARK;
WindowDropdownShowText(
{ windowPos.x + widget.left, windowPos.y + widget.top }, widget.height() + 1, colours[1], 0, 2);
if (GetGameState().Park.IsOpen())
{
gDropdownDefaultIndex = 0;
Dropdown::SetChecked(1, true);
}
else
{
gDropdownDefaultIndex = 1;
Dropdown::SetChecked(0, true);
}
}
}
void OnDropdownEntrance(WidgetIndex widgetIndex, int32_t dropdownIndex)
{
if (widgetIndex == WIDX_OPEN_OR_CLOSE)
{
if (dropdownIndex == -1)
dropdownIndex = gDropdownHighlightedIndex;
if (dropdownIndex != 0)
{
Park::SetOpen(true);
}
else
{
Park::SetOpen(false);
}
}
}
void OnUpdateEntrance()
{
frame_no++;
WidgetInvalidate(*this, WIDX_TAB_1);
}
void OnTextInputEntrance(WidgetIndex widgetIndex, std::string_view text)
{
if (widgetIndex == WIDX_RENAME && !text.empty())
{
auto action = ParkSetNameAction(std::string(text));
GameActions::Execute(&action);
}
}
void OnPrepareDrawEntrance()
{
const auto& gameState = GetGameState();
widgets = _pagedWidgets[page];
InitScrollWidgets();
SetPressedTab();
// Set open / close park button state
{
auto parkName = OpenRCT2::GetGameState().Park.Name.c_str();
auto ft = Formatter::Common();
ft.Add<StringId>(STR_STRING);
ft.Add<const char*>(parkName);
}
const bool parkIsOpen = gameState.Park.IsOpen();
widgets[WIDX_OPEN_OR_CLOSE].image = ImageId(parkIsOpen ? SPR_OPEN : SPR_CLOSED);
const auto closeLightImage = SPR_G2_RCT1_CLOSE_BUTTON_0 + !parkIsOpen * 2
+ WidgetIsPressed(*this, WIDX_CLOSE_LIGHT);
widgets[WIDX_CLOSE_LIGHT].image = ImageId(closeLightImage);
const auto openLightImage = SPR_G2_RCT1_OPEN_BUTTON_0 + parkIsOpen * 2 + WidgetIsPressed(*this, WIDX_OPEN_LIGHT);
widgets[WIDX_OPEN_LIGHT].image = ImageId(openLightImage);
// Only allow closing of park for guest / rating objective
if (gameState.ScenarioObjective.Type == OBJECTIVE_GUESTS_AND_RATING)
disabled_widgets |= (1uLL << WIDX_OPEN_OR_CLOSE) | (1uLL << WIDX_CLOSE_LIGHT) | (1uLL << WIDX_OPEN_LIGHT);
else
disabled_widgets &= ~((1uLL << WIDX_OPEN_OR_CLOSE) | (1uLL << WIDX_CLOSE_LIGHT) | (1uLL << WIDX_OPEN_LIGHT));
// Only allow purchase of land when there is money
if (GetGameState().Park.Flags & PARK_FLAGS_NO_MONEY)
widgets[WIDX_BUY_LAND_RIGHTS].type = WindowWidgetType::Empty;
else
widgets[WIDX_BUY_LAND_RIGHTS].type = WindowWidgetType::FlatBtn;
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7);
AnchorBorderWidgets();
// Anchor entrance page specific widgets
widgets[WIDX_VIEWPORT].right = width - 26;
widgets[WIDX_VIEWPORT].bottom = height - 14;
widgets[WIDX_STATUS].right = width - 26;
widgets[WIDX_STATUS].top = height - 13;
widgets[WIDX_STATUS].bottom = height - 3;
auto y = 0;
if (ThemeGetFlags() & UITHEME_FLAG_USE_LIGHTS_PARK)
{
widgets[WIDX_OPEN_OR_CLOSE].type = WindowWidgetType::Empty;
if (gameState.ScenarioObjective.Type == OBJECTIVE_GUESTS_AND_RATING)
{
widgets[WIDX_CLOSE_LIGHT].type = WindowWidgetType::FlatBtn;
widgets[WIDX_OPEN_LIGHT].type = WindowWidgetType::FlatBtn;
}
else
{
widgets[WIDX_CLOSE_LIGHT].type = WindowWidgetType::ImgBtn;
widgets[WIDX_OPEN_LIGHT].type = WindowWidgetType::ImgBtn;
}
y = widgets[WIDX_OPEN_LIGHT].bottom + 5;
}
else
{
widgets[WIDX_OPEN_OR_CLOSE].type = WindowWidgetType::FlatBtn;
widgets[WIDX_CLOSE_LIGHT].type = WindowWidgetType::Empty;
widgets[WIDX_OPEN_LIGHT].type = WindowWidgetType::Empty;
y = 49;
}
for (int32_t i = WIDX_CLOSE_LIGHT; i <= WIDX_OPEN_LIGHT; i++)
{
widgets[i].left = width - 20;
widgets[i].right = width - 7;
}
for (int32_t i = WIDX_OPEN_OR_CLOSE; i <= WIDX_RENAME; i++)
{
if (widgets[i].type == WindowWidgetType::Empty)
continue;
widgets[i].left = width - 25;
widgets[i].right = width - 2;
widgets[i].top = y;
widgets[i].bottom = y + 23;
y += 24;
}
}
void OnDrawEntrance(DrawPixelInfo& dpi)
{
DrawWidgets(dpi);
DrawTabImages(dpi);
// Draw viewport
if (viewport != nullptr)
{
WindowDrawViewport(dpi, *this);
if (viewport->flags & VIEWPORT_FLAG_SOUND_ON)
GfxDrawSprite(dpi, ImageId(SPR_HEARING_VIEWPORT), WindowGetViewportSoundIconPos(*this));
}
// Draw park closed / open label
auto ft = Formatter();
ft.Add<StringId>(GetGameState().Park.IsOpen() ? STR_PARK_OPEN : STR_PARK_CLOSED);
auto* labelWidget = &widgets[WIDX_STATUS];
DrawTextEllipsised(
dpi, windowPos + ScreenCoordsXY{ labelWidget->midX(), labelWidget->top }, labelWidget->width(),
STR_BLACK_STRING, ft, { TextAlignment::CENTRE });
}
void InitViewport()
{
if (page != WINDOW_PARK_PAGE_ENTRANCE)
return;
const auto& gameState = GetGameState();
std::optional<Focus> newFocus = std::nullopt;
if (!gameState.Park.Entrances.empty())
{
const auto& entrance = gameState.Park.Entrances[0];
newFocus = Focus(CoordsXYZ{ entrance.x + 16, entrance.y + 16, entrance.z + 32 });
}
int32_t viewportFlags{};
if (viewport == nullptr)
{
viewportFlags = gConfigGeneral.AlwaysShowGridlines ? VIEWPORT_FLAG_GRIDLINES : VIEWPORT_FLAG_NONE;
}
else
{
viewportFlags = viewport->flags;
RemoveViewport();
}
// Call invalidate event
OnPrepareDraw();
focus = newFocus;
if (focus.has_value())
{
// Create viewport
if (viewport == nullptr)
{
Widget* viewportWidget = &widgets[WIDX_VIEWPORT];
ViewportCreate(
this, windowPos + ScreenCoordsXY{ viewportWidget->left + 1, viewportWidget->top + 1 },
viewportWidget->width() - 1, viewportWidget->height() - 1, focus.value());
flags |= WF_NO_SCROLLING;
Invalidate();
}
}
if (viewport != nullptr)
viewport->flags = viewportFlags;
Invalidate();
}
#pragma endregion
#pragma region Rating page
void OnResizeRating()
{
WindowSetResize(*this, 255, 182, 255, 182);
}
void OnUpdateRating()
{
frame_no++;
WidgetInvalidate(*this, WIDX_TAB_2);
}
void OnPrepareDrawRating()
{
auto* ratingWidgets = _pagedWidgets[page];
if (ratingWidgets != widgets)
{
widgets = ratingWidgets;
InitScrollWidgets();
}
SetPressedTab();
PrepareWindowTitleText();
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7);
AnchorBorderWidgets();
}
void OnDrawRating(DrawPixelInfo& dpi)
{
DrawWidgets(dpi);
DrawTabImages(dpi);
auto screenPos = windowPos;
Widget* widget = &widgets[WIDX_PAGE_BACKGROUND];
// 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);
// 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);
// 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);
}
#pragma endregion
#pragma region Guests page
void OnResizeGuests()
{
WindowSetResize(*this, 255, 182, 255, 182);
}
void OnUpdateGuests()
{
frame_no++;
_peepAnimationFrame = (_peepAnimationFrame + 1) % 24;
WidgetInvalidate(*this, WIDX_TAB_3);
}
void OnPrepareDrawGuests()
{
auto* guestsWidgets = _pagedWidgets[page];
if (widgets != guestsWidgets)
{
widgets = guestsWidgets;
InitScrollWidgets();
}
SetPressedTab();
PrepareWindowTitleText();
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7);
AnchorBorderWidgets();
}
void OnDrawGuests(DrawPixelInfo& dpi)
{
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<uint32_t>(gameState.NumGuestsInPark);
DrawTextBasic(dpi, screenPos + 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);
// 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++)
{
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();
}
}
Graph::Draw(dpi, cappedHistory, static_cast<int32_t>(std::size(cappedHistory)), screenPos);
}
#pragma endregion
#pragma region Price page
void OnResizePrice()
{
WindowSetResize(*this, 230, 124, 230, 124);
}
void OnMouseDownPrice(WidgetIndex widgetIndex)
{
const auto& gameState = GetGameState();
switch (widgetIndex)
{
case WIDX_INCREASE_PRICE:
{
const auto newFee = std::min(MAX_ENTRANCE_FEE, gameState.Park.EntranceFee + 1.00_GBP);
auto gameAction = ParkSetEntranceFeeAction(newFee);
GameActions::Execute(&gameAction);
break;
}
case WIDX_DECREASE_PRICE:
{
const auto newFee = std::max(0.00_GBP, gameState.Park.EntranceFee - 1.00_GBP);
auto gameAction = ParkSetEntranceFeeAction(newFee);
GameActions::Execute(&gameAction);
break;
}
}
}
void OnUpdatePrice()
{
frame_no++;
WidgetInvalidate(*this, WIDX_TAB_4);
}
void OnPrepareDrawPrice()
{
auto* priceWidgets = _pagedWidgets[page];
if (widgets != priceWidgets)
{
widgets = priceWidgets;
InitScrollWidgets();
}
SetPressedTab();
PrepareWindowTitleText();
// Show a tooltip if the park is pay per ride.
widgets[WIDX_PRICE_LABEL].tooltip = STR_NONE;
widgets[WIDX_PRICE].tooltip = STR_NONE;
if (!Park::EntranceFeeUnlocked())
{
widgets[WIDX_PRICE_LABEL].tooltip = STR_ADMISSION_PRICE_PAY_PER_RIDE_TIP;
widgets[WIDX_PRICE].tooltip = STR_ADMISSION_PRICE_PAY_PER_RIDE_TIP;
}
// If the entry price is locked at free, disable the widget, unless the unlock_all_prices cheat is active.
if ((GetGameState().Park.Flags & PARK_FLAGS_NO_MONEY) || !Park::EntranceFeeUnlocked())
{
widgets[WIDX_PRICE].type = WindowWidgetType::LabelCentred;
widgets[WIDX_INCREASE_PRICE].type = WindowWidgetType::Empty;
widgets[WIDX_DECREASE_PRICE].type = WindowWidgetType::Empty;
}
else
{
widgets[WIDX_PRICE].type = WindowWidgetType::Spinner;
widgets[WIDX_INCREASE_PRICE].type = WindowWidgetType::Button;
widgets[WIDX_DECREASE_PRICE].type = WindowWidgetType::Button;
}
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7);
AnchorBorderWidgets();
}
void OnDrawPrice(DrawPixelInfo& dpi)
{
DrawWidgets(dpi);
DrawTabImages(dpi);
auto screenCoords = windowPos
+ ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, widgets[WIDX_PAGE_BACKGROUND].top + 30 };
auto ft = Formatter();
ft.Add<money64>(GetGameState().TotalIncomeFromAdmissions);
DrawTextBasic(dpi, screenCoords, STR_INCOME_FROM_ADMISSIONS, ft);
money64 parkEntranceFee = Park::GetEntranceFee();
auto stringId = parkEntranceFee == 0 ? STR_FREE : STR_BOTTOM_TOOLBAR_CASH;
screenCoords = windowPos + ScreenCoordsXY{ widgets[WIDX_PRICE].left + 1, widgets[WIDX_PRICE].top + 1 };
ft = Formatter();
ft.Add<money64>(parkEntranceFee);
DrawTextBasic(dpi, screenCoords, stringId, ft, { colours[1] });
}
#pragma endregion
#pragma region Stats page
void OnResizeStats()
{
WindowSetResize(*this, 230, 119, 230, 119);
}
void OnUpdateStats()
{
frame_no++;
WidgetInvalidate(*this, WIDX_TAB_5);
// Invalidate ride count if changed
const auto rideCount = RideGetCount();
if (_numberOfRides != rideCount)
{
_numberOfRides = rideCount;
WidgetInvalidate(*this, WIDX_PAGE_BACKGROUND);
}
// Invalidate number of staff if changed
const auto staffCount = PeepGetStaffCount();
if (_numberOfStaff != staffCount)
{
_numberOfStaff = staffCount;
WidgetInvalidate(*this, WIDX_PAGE_BACKGROUND);
}
}
void OnPrepareDrawStats()
{
auto* statsWidgets = _pagedWidgets[page];
if (widgets != statsWidgets)
{
widgets = statsWidgets;
InitScrollWidgets();
}
SetPressedTab();
PrepareWindowTitleText();
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7);
AnchorBorderWidgets();
}
void OnDrawStats(DrawPixelInfo& dpi)
{
DrawWidgets(dpi);
DrawTabImages(dpi);
auto screenCoords = windowPos
+ ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, widgets[WIDX_PAGE_BACKGROUND].top + 4 };
auto& gameState = GetGameState();
// Draw park size
auto parkSize = gameState.Park.Size * 10;
auto stringIndex = STR_PARK_SIZE_METRIC_LABEL;
if (gConfigGeneral.MeasurementFormat == MeasurementFormat::Imperial)
{
stringIndex = STR_PARK_SIZE_IMPERIAL_LABEL;
parkSize = SquaredMetresToSquaredFeet(parkSize);
}
auto ft = Formatter();
ft.Add<uint32_t>(parkSize);
DrawTextBasic(dpi, screenCoords, stringIndex, ft);
screenCoords.y += LIST_ROW_HEIGHT;
// Draw number of rides / attractions
if (_numberOfRides != -1)
{
ft = Formatter();
ft.Add<uint32_t>(_numberOfRides);
DrawTextBasic(dpi, screenCoords, STR_NUMBER_OF_RIDES_LABEL, ft);
}
screenCoords.y += LIST_ROW_HEIGHT;
// Draw number of staff
if (_numberOfStaff != -1)
{
ft = Formatter();
ft.Add<uint32_t>(_numberOfStaff);
DrawTextBasic(dpi, screenCoords, STR_STAFF_LABEL, ft);
}
screenCoords.y += LIST_ROW_HEIGHT;
// Draw number of guests in park
ft = Formatter();
ft.Add<uint32_t>(gameState.NumGuestsInPark);
DrawTextBasic(dpi, screenCoords, STR_GUESTS_IN_PARK_LABEL, ft);
screenCoords.y += LIST_ROW_HEIGHT;
ft = Formatter();
ft.Add<uint32_t>(gameState.TotalAdmissions);
DrawTextBasic(dpi, screenCoords, STR_TOTAL_ADMISSIONS, ft);
}
#pragma endregion
#pragma region Objective page
void OnMouseUpObjective(WidgetIndex widgetIndex)
{
switch (widgetIndex)
{
case WIDX_ENTER_NAME:
WindowTextInputOpen(
this, WIDX_ENTER_NAME, STR_ENTER_NAME, STR_PLEASE_ENTER_YOUR_NAME_FOR_THE_SCENARIO_CHART, {}, 0, 0,
ParkNameMaxLength);
break;
}
}
void OnResizeObjective()
{
#ifndef NO_TTF
if (gCurrentTTFFontSet != nullptr)
WindowSetResize(*this, 230, 270, 230, 270);
else
#endif
WindowSetResize(*this, 230, 226, 230, 226);
}
void OnUpdateObjective()
{
frame_no++;
WidgetInvalidate(*this, WIDX_TAB_6);
}
void OnTextInputObjective(WidgetIndex widgetIndex, std::string_view text)
{
if (widgetIndex == WIDX_ENTER_NAME && !text.empty())
{
std::string strText(text);
ScenarioSuccessSubmitName(GetGameState(), strText.c_str());
Invalidate();
}
}
void OnPrepareDrawObjective()
{
SetPressedTab();
PrepareWindowTitleText();
// Show name input button on scenario completion.
if (GetGameState().Park.Flags & PARK_FLAGS_SCENARIO_COMPLETE_NAME_INPUT)
{
widgets[WIDX_ENTER_NAME].type = WindowWidgetType::Button;
widgets[WIDX_ENTER_NAME].top = height - 19;
widgets[WIDX_ENTER_NAME].bottom = height - 6;
}
else
widgets[WIDX_ENTER_NAME].type = WindowWidgetType::Empty;
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7);
AnchorBorderWidgets();
}
void OnDrawObjective(DrawPixelInfo& dpi)
{
auto& gameState = GetGameState();
DrawWidgets(dpi);
DrawTabImages(dpi);
// Scenario description
auto screenCoords = windowPos
+ ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, widgets[WIDX_PAGE_BACKGROUND].top + 7 };
auto ft = Formatter();
ft.Add<StringId>(STR_STRING);
ft.Add<const char*>(gameState.ScenarioDetails.c_str());
screenCoords.y += DrawTextWrapped(dpi, screenCoords, 222, STR_BLACK_STRING, ft);
screenCoords.y += 5;
// Your objective:
DrawTextBasic(dpi, screenCoords, STR_OBJECTIVE_LABEL);
screenCoords.y += LIST_ROW_HEIGHT;
// Objective
ft = Formatter();
formatObjective(ft, gameState.ScenarioObjective);
screenCoords.y += DrawTextWrapped(dpi, screenCoords, 221, ObjectiveNames[gameState.ScenarioObjective.Type], ft);
screenCoords.y += 5;
// Objective outcome
if (gameState.ScenarioCompletedCompanyValue != kMoney64Undefined)
{
if (gameState.ScenarioCompletedCompanyValue == COMPANY_VALUE_ON_FAILED_OBJECTIVE)
{
// Objective failed
DrawTextWrapped(dpi, screenCoords, 222, STR_OBJECTIVE_FAILED);
}
else
{
// Objective completed
ft = Formatter();
ft.Add<money64>(gameState.ScenarioCompletedCompanyValue);
DrawTextWrapped(dpi, screenCoords, 222, STR_OBJECTIVE_ACHIEVED, ft);
}
}
}
#pragma endregion
#pragma region Awards page
void OnResizeAwards()
{
WindowSetResize(*this, 230, 182, 230, 182);
}
void OnUpdateAwards()
{
frame_no++;
WidgetInvalidate(*this, WIDX_TAB_7);
}
void OnPrepareDrawAwards()
{
auto* awardsWidgets = _pagedWidgets[page];
if (widgets != awardsWidgets)
{
widgets = awardsWidgets;
InitScrollWidgets();
}
SetPressedTab();
PrepareWindowTitleText();
WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7);
AnchorBorderWidgets();
}
void OnDrawAwards(DrawPixelInfo& dpi)
{
DrawWidgets(dpi);
DrawTabImages(dpi);
auto screenCoords = windowPos
+ ScreenCoordsXY{ widgets[WIDX_PAGE_BACKGROUND].left + 4, widgets[WIDX_PAGE_BACKGROUND].top + 4 };
auto& currentAwards = OpenRCT2::GetGameState().CurrentAwards;
for (const auto& award : currentAwards)
{
GfxDrawSprite(dpi, ImageId(_parkAwards[EnumValue(award.Type)].sprite), screenCoords);
DrawTextWrapped(dpi, screenCoords + ScreenCoordsXY{ 34, 6 }, 180, _parkAwards[EnumValue(award.Type)].text);
screenCoords.y += 32;
}
if (currentAwards.empty())
DrawTextBasic(dpi, screenCoords + ScreenCoordsXY{ 6, 6 }, STR_NO_RECENT_AWARDS);
}
#pragma endregion
#pragma region Common
void SetPage(int32_t newPage)
{
if (InputTestFlag(INPUT_FLAG_TOOL_ACTIVE))
if (classification == gCurrentToolWidget.window_classification && number == gCurrentToolWidget.window_number)
ToolCancel();
// Set listen only to viewport
bool listen = false;
if (newPage == WINDOW_PARK_PAGE_ENTRANCE && viewport != nullptr && !(viewport->flags & VIEWPORT_FLAG_SOUND_ON))
listen = true;
page = newPage;
frame_no = 0;
_peepAnimationFrame = 0;
RemoveViewport();
hold_down_widgets = _pagedHoldDownWidgets[newPage];
widgets = _pagedWidgets[newPage];
SetDisabledTabs();
Invalidate();
OnResize();
OnPrepareDraw();
OnUpdate();
if (listen && viewport != nullptr)
viewport->flags |= VIEWPORT_FLAG_SOUND_ON;
}
void AnchorBorderWidgets()
{
ResizeFrameWithPage();
}
void SetPressedTab()
{
for (int32_t i = WIDX_TAB_1; i <= WIDX_TAB_7; i++)
pressed_widgets &= ~(1 << i);
pressed_widgets |= 1LL << (WIDX_TAB_1 + page);
}
void DrawTabImages(DrawPixelInfo& dpi)
{
// Entrance tab
if (!WidgetIsDisabled(*this, WIDX_TAB_1))
{
GfxDrawSprite(
dpi, ImageId(SPR_TAB_PARK_ENTRANCE),
windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_1].left, widgets[WIDX_TAB_1].top });
}
// Rating tab
if (!WidgetIsDisabled(*this, WIDX_TAB_2))
{
ImageId spriteIdx(SPR_TAB_GRAPH_0);
if (page == WINDOW_PARK_PAGE_RATING)
spriteIdx = spriteIdx.WithIndexOffset((frame_no / 8) % 8);
GfxDrawSprite(dpi, spriteIdx, windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_2].left, widgets[WIDX_TAB_2].top });
GfxDrawSprite(
dpi, ImageId(SPR_RATING_HIGH),
windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_2].left + 7, widgets[WIDX_TAB_2].top + 1 });
GfxDrawSprite(
dpi, ImageId(SPR_RATING_LOW),
windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_2].left + 16, widgets[WIDX_TAB_2].top + 12 });
}
// Guests tab
if (!WidgetIsDisabled(*this, WIDX_TAB_3))
{
ImageId spriteIdx(SPR_TAB_GRAPH_0);
if (page == WINDOW_PARK_PAGE_GUESTS)
spriteIdx = spriteIdx.WithIndexOffset((frame_no / 8) % 8);
GfxDrawSprite(dpi, spriteIdx, windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_3].left, widgets[WIDX_TAB_3].top });
ImageId peepImage(GetPeepAnimation(PeepSpriteType::Normal).base_image + 1, COLOUR_BRIGHT_RED, COLOUR_TEAL);
if (page == WINDOW_PARK_PAGE_GUESTS)
peepImage = peepImage.WithIndexOffset(_peepAnimationFrame & 0xFFFFFFFC);
GfxDrawSprite(
dpi, peepImage, windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_3].midX(), widgets[WIDX_TAB_3].bottom - 9 });
}
// Price tab
if (!WidgetIsDisabled(*this, WIDX_TAB_4))
{
ImageId spriteIdx(SPR_TAB_ADMISSION_0);
if (page == WINDOW_PARK_PAGE_PRICE)
spriteIdx = spriteIdx.WithIndexOffset((frame_no / 2) % 8);
GfxDrawSprite(dpi, spriteIdx, windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_4].left, widgets[WIDX_TAB_4].top });
}
// Statistics tab
if (!WidgetIsDisabled(*this, WIDX_TAB_5))
{
ImageId spriteIdx(SPR_TAB_STATS_0);
if (page == WINDOW_PARK_PAGE_STATS)
spriteIdx = spriteIdx.WithIndexOffset((frame_no / 4) % 7);
GfxDrawSprite(dpi, spriteIdx, windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_5].left, widgets[WIDX_TAB_5].top });
}
// Objective tab
if (!WidgetIsDisabled(*this, WIDX_TAB_6))
{
ImageId spriteIdx(SPR_TAB_OBJECTIVE_0);
if (page == WINDOW_PARK_PAGE_OBJECTIVE)
spriteIdx = spriteIdx.WithIndexOffset((frame_no / 4) % 16);
GfxDrawSprite(dpi, spriteIdx, windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_6].left, widgets[WIDX_TAB_6].top });
}
// Awards tab
if (!WidgetIsDisabled(*this, WIDX_TAB_7))
{
GfxDrawSprite(
dpi, ImageId(SPR_TAB_AWARDS),
windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_7].left, widgets[WIDX_TAB_7].top });
}
}
};
static ParkWindow* ParkWindowOpen(uint8_t page)
{
auto* wnd = WindowFocusOrCreate<ParkWindow>(WindowClass::ParkInformation, 230, 174 + 9, WF_10);
if (wnd != nullptr && page != WINDOW_PARK_PAGE_ENTRANCE)
{
wnd->OnMouseUp(WIDX_TAB_1 + page);
}
return wnd;
}
/**
*
* rct2: 0x00667C48
*/
WindowBase* ParkEntranceOpen()
{
return ParkWindowOpen(WINDOW_PARK_PAGE_ENTRANCE);
}
/**
*
* rct2: 0x00667CA4
*/
WindowBase* ParkRatingOpen()
{
return ParkWindowOpen(WINDOW_PARK_PAGE_RATING);
}
/**
*
* rct2: 0x00667D35
*/
WindowBase* ParkGuestsOpen()
{
return ParkWindowOpen(WINDOW_PARK_PAGE_GUESTS);
}
/**
*
* rct2: 0x00667E57
*/
WindowBase* ParkObjectiveOpen()
{
auto* wnd = ParkWindowOpen(WINDOW_PARK_PAGE_OBJECTIVE);
if (wnd != nullptr)
{
wnd->Invalidate();
wnd->windowPos.x = ContextGetWidth() / 2 - 115;
wnd->windowPos.y = ContextGetHeight() / 2 - 87;
wnd->Invalidate();
}
return wnd;
}
/**
*
* rct2: 0x00667DC6
*/
WindowBase* ParkAwardsOpen()
{
return ParkWindowOpen(WINDOW_PARK_PAGE_AWARDS);
}
} // namespace OpenRCT2::Ui::Windows