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

401 lines
14 KiB
C++
Raw Normal View History

/*****************************************************************************
* Copyright (c) 2014-2022 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/Widget.h>
#include <openrct2-ui/windows/Window.h>
#include <openrct2/Game.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/ParkMarketingAction.h>
#include <openrct2/core/BitSet.hpp>
#include <openrct2/drawing/Drawing.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>
2017-12-31 13:21:34 +01:00
#include <openrct2/ride/Ride.h>
2018-01-10 00:00:09 +01:00
#include <openrct2/ride/RideData.h>
#include <openrct2/ride/ShopItem.h>
2014-08-12 01:27:25 +02:00
using namespace OpenRCT2;
2022-07-31 14:22:58 +02:00
static constexpr const StringId WINDOW_TITLE = STR_NONE;
2020-07-30 21:54:17 +02:00
static constexpr const int32_t WH = 109;
2020-05-05 22:26:14 +02:00
static constexpr const int32_t WW = 350;
2021-08-02 06:24:52 +02:00
constexpr uint16_t SELECTED_ITEM_UNDEFINED = 0xFFFF;
2014-08-12 01:27:25 +02:00
// clang-format off
enum WindowNewCampaignWidgetIdx {
WIDX_BACKGROUND,
WIDX_TITLE,
WIDX_CLOSE,
WIDX_RIDE_LABEL,
WIDX_RIDE_DROPDOWN,
WIDX_RIDE_DROPDOWN_BUTTON,
WIDX_WEEKS_LABEL,
WIDX_WEEKS_SPINNER,
WIDX_WEEKS_INCREASE_BUTTON,
WIDX_WEEKS_DECREASE_BUTTON,
WIDX_START_BUTTON
2014-08-12 01:27:25 +02:00
};
static rct_widget window_new_campaign_widgets[] = {
2020-05-09 16:44:21 +02:00
WINDOW_SHIM(WINDOW_TITLE, WW, WH),
MakeWidget ({ 14, 24}, {126, 12}, WindowWidgetType::Label, WindowColour::Primary, STR_EMPTY ), // ride label
MakeWidget ({100, 24}, {242, 12}, WindowWidgetType::DropdownMenu, WindowColour::Primary, STR_EMPTY ), // ride dropdown
MakeWidget ({330, 25}, { 11, 10}, WindowWidgetType::Button, WindowColour::Primary, STR_DROPDOWN_GLYPH ), // ride dropdown button
MakeWidget ({ 14, 41}, {126, 14}, WindowWidgetType::Label, WindowColour::Primary, STR_LENGTH_OF_TIME ), // weeks label
MakeSpinnerWidgets({120, 41}, {100, 14}, WindowWidgetType::Spinner, WindowColour::Primary, STR_EMPTY ), // weeks (3 widgets)
MakeWidget ({ 14, 89}, {322, 14}, WindowWidgetType::Button, WindowColour::Primary, STR_MARKETING_START_THIS_MARKETING_CAMPAIGN), // start button
2021-09-26 11:11:42 +02:00
WIDGETS_END,
2014-08-12 01:27:25 +02:00
};
// clang-format on
2014-08-12 01:27:25 +02:00
2021-03-13 16:51:54 +01:00
class NewCampaignWindow final : public Window
2014-08-12 01:27:25 +02:00
{
2021-03-13 16:51:54 +01:00
private:
2022-01-19 14:17:11 +01:00
std::vector<RideId> RideList;
2021-03-13 16:51:54 +01:00
std::vector<ShopItem> ShopItems;
2022-01-19 14:17:11 +01:00
static bool RideValueCompare(const RideId& a, const RideId& b)
2018-06-22 23:21:44 +02:00
{
2021-03-13 16:51:54 +01:00
auto valueA = 0;
auto rideA = get_ride(a);
if (rideA != nullptr)
valueA = rideA->value;
2021-03-13 16:51:54 +01:00
auto valueB = 0;
auto rideB = get_ride(b);
if (rideB != nullptr)
valueB = rideB->value;
2021-03-13 16:51:54 +01:00
return (valueB - valueA) < 0;
}
2022-01-19 14:17:11 +01:00
static int32_t RideNameCompare(const RideId& a, const RideId& b)
2021-03-13 16:51:54 +01:00
{
std::string rideAName = "";
auto rideA = get_ride(a);
if (rideA != nullptr)
rideAName = rideA->GetName();
2021-03-13 16:51:54 +01:00
std::string rideBName = "";
auto rideB = get_ride(b);
if (rideB != nullptr)
rideBName = rideB->GetName();
2021-03-13 16:51:54 +01:00
return strlogicalcmp(rideAName.c_str(), rideBName.c_str()) < 0;
}
2014-08-12 01:27:25 +02:00
2021-03-13 16:51:54 +01:00
/**
*
* rct2: 0x0069E320
*/
void GetShopItems()
{
BitSet<EnumValue(ShopItem::Count)> items = {};
2021-03-13 16:51:54 +01:00
for (auto& curRide : GetRideManager())
{
2021-03-13 16:51:54 +01:00
auto rideEntry = curRide.GetRideEntry();
if (rideEntry != nullptr)
2019-08-04 17:00:15 +02:00
{
2021-03-13 16:51:54 +01:00
for (const auto itemType : rideEntry->shop_item)
{
2021-03-13 16:51:54 +01:00
if (itemType != ShopItem::None && GetShopItemDescriptor(itemType).IsFoodOrDrink())
{
items[EnumValue(itemType)] = true;
}
}
2019-08-04 17:00:15 +02:00
}
}
ShopItems.clear();
2021-03-13 16:51:54 +01:00
for (auto i = 0; i < EnumValue(ShopItem::Count); i++)
{
2021-03-13 16:51:54 +01:00
if (items[i])
{
ShopItems.push_back(ShopItem(i));
}
}
}
2014-08-12 01:27:25 +02:00
2021-03-13 16:51:54 +01:00
public:
void RefreshRides()
{
2021-03-13 16:51:54 +01:00
// Get all applicable rides
RideList.clear();
for (const auto& curRide : GetRideManager())
{
if (curRide.status == RideStatus::Open)
2021-03-13 16:51:54 +01:00
{
if (!curRide.GetRideTypeDescriptor().HasFlag(
RIDE_TYPE_FLAG_IS_SHOP_OR_FACILITY | RIDE_TYPE_FLAG_SELLS_FOOD | RIDE_TYPE_FLAG_SELLS_DRINKS
2021-03-13 16:51:54 +01:00
| RIDE_TYPE_FLAG_IS_TOILET))
{
2021-03-13 16:51:54 +01:00
RideList.push_back(curRide.id);
2018-06-22 23:21:44 +02:00
}
2021-03-13 16:51:54 +01:00
}
}
2021-03-13 16:51:54 +01:00
if (RideList.size() > Dropdown::ItemsMaxSize)
{
std::sort(RideList.begin(), RideList.end(), RideValueCompare);
RideList.resize(Dropdown::ItemsMaxSize);
}
// Sort rides by name
std::sort(RideList.begin(), RideList.end(), RideNameCompare);
}
2014-08-12 01:27:25 +02:00
2021-03-13 16:51:54 +01:00
void OnOpen() override
{
widgets = window_new_campaign_widgets;
hold_down_widgets = (1ULL << WIDX_WEEKS_INCREASE_BUTTON) | (1ULL << WIDX_WEEKS_DECREASE_BUTTON);
WindowInitScrollWidgets(*this);
2021-03-13 16:51:54 +01:00
}
2021-03-13 16:51:54 +01:00
void SetCampaign(int16_t campaignType)
2018-06-22 23:21:44 +02:00
{
2021-03-13 16:51:54 +01:00
widgets[WIDX_TITLE].text = MarketingCampaignNames[campaignType][0];
2021-03-13 16:51:54 +01:00
// Campaign type
campaign.campaign_type = campaignType;
// Number of weeks
campaign.no_weeks = 2;
// Currently selected ride
campaign.RideId = RideId::GetNull();
2021-03-13 16:51:54 +01:00
RefreshRides();
}
2022-08-21 18:49:23 +02:00
void OnMouseDown(WidgetIndex widgetIndex) override
2021-03-13 16:51:54 +01:00
{
rct_widget* widget = &widgets[widgetIndex];
rct_widget* dropdownWidget;
switch (widgetIndex)
{
case WIDX_RIDE_DROPDOWN_BUTTON:
dropdownWidget = widget - 1;
if (campaign.campaign_type == ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE)
2018-06-22 23:21:44 +02:00
{
2021-03-13 16:51:54 +01:00
GetShopItems();
if (!ShopItems.empty())
2018-06-22 23:21:44 +02:00
{
2021-03-13 16:51:54 +01:00
int32_t numItems = 0;
int32_t maxSize = std::min(Dropdown::ItemsMaxSize, static_cast<int32_t>(ShopItems.size()));
for (int32_t i = 0; i < maxSize; i++)
{
gDropdownItems[i].Format = STR_DROPDOWN_MENU_LABEL;
gDropdownItems[i].Args = GetShopItemDescriptor(ShopItems[i]).Naming.Plural;
2021-03-13 16:51:54 +01:00
numItems++;
}
2018-06-22 23:21:44 +02:00
2021-03-13 16:51:54 +01:00
WindowDropdownShowTextCustomWidth(
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top },
dropdownWidget->height() + 1, colours[1], 0, Dropdown::Flag::StayOpen, numItems,
dropdownWidget->width() - 3);
2018-06-22 23:21:44 +02:00
}
}
2021-03-13 16:51:54 +01:00
else
2018-06-22 23:21:44 +02:00
{
2021-03-13 16:51:54 +01:00
int32_t numItems = 0;
for (auto rideIndex : RideList)
2019-07-21 13:09:23 +02:00
{
auto curRide = get_ride(rideIndex);
2021-03-13 16:51:54 +01:00
if (curRide != nullptr)
{
2021-03-13 16:51:54 +01:00
// HACK until dropdown items have longer argument buffers
gDropdownItems[numItems].Format = STR_DROPDOWN_MENU_LABEL;
Formatter ft(reinterpret_cast<uint8_t*>(&gDropdownItems[numItems].Args));
2021-03-13 16:51:54 +01:00
if (curRide->custom_name.empty())
{
curRide->FormatNameTo(ft);
}
else
{
gDropdownItems[numItems].Format = STR_OPTIONS_DROPDOWN_ITEM;
2021-03-13 16:51:54 +01:00
ft.Add<const char*>(curRide->custom_name.c_str());
}
numItems++;
}
2019-07-21 13:09:23 +02:00
}
2021-03-13 16:51:54 +01:00
WindowDropdownShowTextCustomWidth(
{ windowPos.x + dropdownWidget->left, windowPos.y + dropdownWidget->top }, dropdownWidget->height() + 1,
colours[1], 0, Dropdown::Flag::StayOpen, numItems, dropdownWidget->width() - 3);
}
2021-03-13 16:51:54 +01:00
break;
// In RCT2, the maximum was 6 weeks
case WIDX_WEEKS_INCREASE_BUTTON:
campaign.no_weeks = std::min(campaign.no_weeks + 1, 12);
Invalidate();
break;
case WIDX_WEEKS_DECREASE_BUTTON:
campaign.no_weeks = std::max(campaign.no_weeks - 1, 2);
Invalidate();
break;
}
}
2022-08-21 18:49:23 +02:00
void OnMouseUp(WidgetIndex widgetIndex) override
2021-03-13 16:51:54 +01:00
{
switch (widgetIndex)
{
case WIDX_CLOSE:
Close();
break;
case WIDX_START_BUTTON:
{
2021-08-02 06:24:52 +02:00
auto gameAction = ParkMarketingAction(
2022-01-19 15:54:57 +01:00
campaign.campaign_type, campaign.RideId.ToUnderlying(), campaign.no_weeks);
2021-03-13 16:51:54 +01:00
gameAction.SetCallback([](const GameAction* ga, const GameActions::Result* result) {
if (result->Error == GameActions::Status::Ok)
{
window_close_by_class(WindowClass::NewCampaign);
2021-03-13 16:51:54 +01:00
}
});
GameActions::Execute(&gameAction);
break;
}
2021-03-13 16:51:54 +01:00
}
}
2022-08-21 18:49:23 +02:00
void OnDropdown(WidgetIndex widgetIndex, int32_t dropdownIndex) override
2018-06-22 23:21:44 +02:00
{
2021-03-13 16:51:54 +01:00
if (widgetIndex != WIDX_RIDE_DROPDOWN_BUTTON)
return;
2021-03-13 16:51:54 +01:00
if (dropdownIndex < 0)
return;
2021-03-13 16:51:54 +01:00
if (campaign.campaign_type == ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE)
{
if (static_cast<size_t>(dropdownIndex) >= ShopItems.size())
return;
2021-03-13 16:51:54 +01:00
campaign.ShopItemId = EnumValue(ShopItems[dropdownIndex]);
}
else
{
if (static_cast<size_t>(dropdownIndex) >= RideList.size())
return;
2014-08-12 01:27:25 +02:00
2021-03-13 16:51:54 +01:00
campaign.RideId = RideList[dropdownIndex];
}
2014-08-12 01:27:25 +02:00
2021-03-13 16:51:54 +01:00
Invalidate();
}
void OnPrepareDraw() override
2018-06-22 23:21:44 +02:00
{
2021-03-13 16:51:54 +01:00
widgets[WIDX_RIDE_LABEL].type = WindowWidgetType::Empty;
widgets[WIDX_RIDE_DROPDOWN].type = WindowWidgetType::Empty;
widgets[WIDX_RIDE_DROPDOWN_BUTTON].type = WindowWidgetType::Empty;
widgets[WIDX_RIDE_DROPDOWN].text = STR_MARKETING_NOT_SELECTED;
switch (campaign.campaign_type)
{
case ADVERTISING_CAMPAIGN_RIDE_FREE:
case ADVERTISING_CAMPAIGN_RIDE:
widgets[WIDX_RIDE_LABEL].type = WindowWidgetType::Label;
widgets[WIDX_RIDE_DROPDOWN].type = WindowWidgetType::DropdownMenu;
widgets[WIDX_RIDE_DROPDOWN_BUTTON].type = WindowWidgetType::Button;
widgets[WIDX_RIDE_LABEL].text = STR_MARKETING_RIDE;
if (campaign.RideId != RideId::GetNull())
2019-07-21 02:28:33 +02:00
{
2021-03-13 16:51:54 +01:00
auto curRide = get_ride(campaign.RideId);
if (curRide != nullptr)
{
widgets[WIDX_RIDE_DROPDOWN].text = STR_STRINGID;
2021-03-13 16:51:54 +01:00
auto ft = Formatter::Common();
curRide->FormatNameTo(ft);
}
2019-07-21 02:28:33 +02:00
}
2021-03-13 16:51:54 +01:00
break;
case ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE:
widgets[WIDX_RIDE_LABEL].type = WindowWidgetType::Label;
widgets[WIDX_RIDE_DROPDOWN].type = WindowWidgetType::DropdownMenu;
widgets[WIDX_RIDE_DROPDOWN_BUTTON].type = WindowWidgetType::Button;
widgets[WIDX_RIDE_LABEL].text = STR_MARKETING_ITEM;
2021-08-02 06:24:52 +02:00
if (campaign.ShopItemId != SELECTED_ITEM_UNDEFINED)
2021-03-13 16:51:54 +01:00
{
widgets[WIDX_RIDE_DROPDOWN].text = GetShopItemDescriptor(ShopItem(campaign.ShopItemId)).Naming.Plural;
}
break;
}
2021-03-13 16:51:54 +01:00
// Set current number of weeks spinner (moved to paint due to required parameter)
widgets[WIDX_WEEKS_SPINNER].text = STR_NONE;
2021-03-13 16:51:54 +01:00
// Enable / disable start button based on ride dropdown
WidgetSetDisabled(*this, WIDX_START_BUTTON, false);
if (widgets[WIDX_RIDE_DROPDOWN].type == WindowWidgetType::DropdownMenu && campaign.RideId == RideId::GetNull())
WidgetSetDisabled(*this, WIDX_START_BUTTON, true);
2021-03-13 16:51:54 +01:00
}
2014-08-12 01:27:25 +02:00
2021-03-13 16:51:54 +01:00
void OnDraw(rct_drawpixelinfo& dpi) override
{
ScreenCoordsXY screenCoords{};
2021-03-13 16:51:54 +01:00
DrawWidgets(dpi);
2021-03-13 16:51:54 +01:00
// Number of weeks
rct_widget* spinnerWidget = &widgets[WIDX_WEEKS_SPINNER];
auto ft = Formatter();
ft.Add<int16_t>(campaign.no_weeks);
2021-03-13 16:51:54 +01:00
DrawTextBasic(
&dpi, windowPos + ScreenCoordsXY{ spinnerWidget->left + 1, spinnerWidget->top },
campaign.no_weeks == 1 ? STR_MARKETING_1_WEEK : STR_X_WEEKS, ft, { colours[0] });
2021-03-13 16:51:54 +01:00
screenCoords = windowPos + ScreenCoordsXY{ 14, 60 };
2021-03-13 16:51:54 +01:00
// Price per week
ft = Formatter();
ft.Add<money64>(AdvertisingCampaignPricePerWeek[campaign.campaign_type]);
DrawTextBasic(&dpi, screenCoords, STR_MARKETING_COST_PER_WEEK, ft);
2021-03-13 16:51:54 +01:00
screenCoords.y += 13;
2021-03-13 16:51:54 +01:00
// Total price
ft = Formatter();
ft.Add<money64>(AdvertisingCampaignPricePerWeek[campaign.campaign_type] * campaign.no_weeks);
DrawTextBasic(&dpi, screenCoords, STR_MARKETING_TOTAL_COST, ft);
2021-03-13 16:51:54 +01:00
}
};
rct_window* WindowNewCampaignOpen(int16_t campaignType)
{
auto w = static_cast<NewCampaignWindow*>(window_bring_to_front_by_class(WindowClass::NewCampaign));
2021-03-13 16:51:54 +01:00
if (w != nullptr)
{
2021-03-13 16:51:54 +01:00
if (w->campaign.campaign_type == campaignType)
return w;
window_close(*w);
}
w = WindowCreate<NewCampaignWindow>(WindowClass::NewCampaign, WW, WH, 0);
2021-03-13 16:51:54 +01:00
if (w != nullptr)
{
2021-03-13 16:51:54 +01:00
w->SetCampaign(campaignType);
}
2021-03-13 16:51:54 +01:00
return w;
}
2021-03-13 16:51:54 +01:00
void WindowCampaignRefreshRides()
{
auto w = static_cast<NewCampaignWindow*>(window_find_by_class(WindowClass::NewCampaign));
2021-03-13 16:51:54 +01:00
if (w != nullptr)
{
2021-03-13 16:51:54 +01:00
w->RefreshRides();
}
}