mirror of https://github.com/OpenRCT2/OpenRCT2.git
263 lines
7.8 KiB
C++
263 lines
7.8 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 "Marketing.h"
|
|
|
|
#include "../Cheats.h"
|
|
#include "../Game.h"
|
|
#include "../GameState.h"
|
|
#include "../config/Config.h"
|
|
#include "../entity/Guest.h"
|
|
#include "../interface/Window.h"
|
|
#include "../localisation/Formatter.h"
|
|
#include "../localisation/Localisation.h"
|
|
#include "../profiling/Profiling.h"
|
|
#include "../ride/Ride.h"
|
|
#include "../ride/RideData.h"
|
|
#include "../ride/ShopItem.h"
|
|
#include "../world/Park.h"
|
|
#include "Finance.h"
|
|
#include "NewsItem.h"
|
|
|
|
using namespace OpenRCT2;
|
|
|
|
const money64 AdvertisingCampaignPricePerWeek[] = {
|
|
50.00_GBP, // PARK_ENTRY_FREE
|
|
50.00_GBP, // RIDE_FREE
|
|
50.00_GBP, // PARK_ENTRY_HALF_PRICE
|
|
50.00_GBP, // FOOD_OR_DRINK_FREE
|
|
350.00_GBP, // PARK
|
|
200.00_GBP, // RIDE
|
|
};
|
|
|
|
static constexpr uint16_t AdvertisingCampaignGuestGenerationProbabilities[] = {
|
|
400, 300, 200, 200, 250, 200,
|
|
};
|
|
|
|
std::vector<MarketingCampaign> gMarketingCampaigns;
|
|
|
|
uint16_t MarketingGetCampaignGuestGenerationProbability(int32_t campaignType)
|
|
{
|
|
auto campaign = MarketingGetCampaign(campaignType);
|
|
if (campaign == nullptr)
|
|
return 0;
|
|
|
|
// Lower probability of guest generation if price was already low
|
|
auto probability = AdvertisingCampaignGuestGenerationProbabilities[campaign->Type];
|
|
switch (campaign->Type)
|
|
{
|
|
case ADVERTISING_CAMPAIGN_PARK_ENTRY_FREE:
|
|
if (Park::GetEntranceFee() < 4.00_GBP)
|
|
probability /= 8;
|
|
break;
|
|
case ADVERTISING_CAMPAIGN_PARK_ENTRY_HALF_PRICE:
|
|
if (Park::GetEntranceFee() < 6.00_GBP)
|
|
probability /= 8;
|
|
break;
|
|
case ADVERTISING_CAMPAIGN_RIDE_FREE:
|
|
{
|
|
auto ride = GetRide(campaign->RideId);
|
|
if (ride == nullptr || ride->price[0] < 0.30_GBP)
|
|
probability /= 8;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return probability;
|
|
}
|
|
|
|
static void MarketingRaiseFinishedNotification(const MarketingCampaign& campaign)
|
|
{
|
|
if (gConfigNotifications.ParkMarketingCampaignFinished)
|
|
{
|
|
Formatter ft;
|
|
// This sets the string parameters for the marketing types that have an argument.
|
|
if (campaign.Type == ADVERTISING_CAMPAIGN_RIDE_FREE || campaign.Type == ADVERTISING_CAMPAIGN_RIDE)
|
|
{
|
|
auto ride = GetRide(campaign.RideId);
|
|
if (ride != nullptr)
|
|
{
|
|
ride->FormatNameTo(ft);
|
|
}
|
|
}
|
|
else if (campaign.Type == ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE)
|
|
{
|
|
ft.Add<StringId>(GetShopItemDescriptor(campaign.ShopItemType).Naming.Plural);
|
|
}
|
|
|
|
News::AddItemToQueue(News::ItemType::Campaign, MarketingCampaignNames[campaign.Type][2], 0, ft);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update status of marketing campaigns and produce a news item when they have finished.
|
|
* rct2: 0x0069E0C1
|
|
*/
|
|
void MarketingUpdate()
|
|
{
|
|
PROFILED_FUNCTION();
|
|
|
|
if (GetGameState().Cheats.NeverendingMarketing)
|
|
return;
|
|
|
|
for (auto it = gMarketingCampaigns.begin(); it != gMarketingCampaigns.end();)
|
|
{
|
|
auto& campaign = *it;
|
|
if (campaign.Flags & MarketingCampaignFlags::FIRST_WEEK)
|
|
{
|
|
// This ensures the campaign is active for x full weeks if started within the
|
|
// middle of a week.
|
|
campaign.Flags &= ~MarketingCampaignFlags::FIRST_WEEK;
|
|
}
|
|
else if (campaign.WeeksLeft > 0)
|
|
{
|
|
campaign.WeeksLeft--;
|
|
}
|
|
|
|
if (campaign.WeeksLeft == 0)
|
|
{
|
|
MarketingRaiseFinishedNotification(campaign);
|
|
it = gMarketingCampaigns.erase(it);
|
|
}
|
|
else
|
|
{
|
|
++it;
|
|
}
|
|
}
|
|
|
|
WindowInvalidateByClass(WindowClass::Finances);
|
|
}
|
|
|
|
void MarketingSetGuestCampaign(Guest* peep, int32_t campaignType)
|
|
{
|
|
auto campaign = MarketingGetCampaign(campaignType);
|
|
if (campaign == nullptr)
|
|
return;
|
|
|
|
switch (campaign->Type)
|
|
{
|
|
case ADVERTISING_CAMPAIGN_PARK_ENTRY_FREE:
|
|
peep->GiveItem(ShopItem::Voucher);
|
|
peep->VoucherType = VOUCHER_TYPE_PARK_ENTRY_FREE;
|
|
break;
|
|
case ADVERTISING_CAMPAIGN_RIDE_FREE:
|
|
peep->GiveItem(ShopItem::Voucher);
|
|
peep->VoucherType = VOUCHER_TYPE_RIDE_FREE;
|
|
peep->VoucherRideId = campaign->RideId;
|
|
peep->GuestHeadingToRideId = campaign->RideId;
|
|
peep->GuestIsLostCountdown = 240;
|
|
break;
|
|
case ADVERTISING_CAMPAIGN_PARK_ENTRY_HALF_PRICE:
|
|
peep->GiveItem(ShopItem::Voucher);
|
|
peep->VoucherType = VOUCHER_TYPE_PARK_ENTRY_HALF_PRICE;
|
|
break;
|
|
case ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE:
|
|
peep->GiveItem(ShopItem::Voucher);
|
|
peep->VoucherType = VOUCHER_TYPE_FOOD_OR_DRINK_FREE;
|
|
peep->VoucherShopItem = campaign->ShopItemType;
|
|
break;
|
|
case ADVERTISING_CAMPAIGN_PARK:
|
|
break;
|
|
case ADVERTISING_CAMPAIGN_RIDE:
|
|
peep->GuestHeadingToRideId = campaign->RideId;
|
|
peep->GuestIsLostCountdown = 240;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool MarketingIsCampaignTypeApplicable(int32_t campaignType)
|
|
{
|
|
switch (campaignType)
|
|
{
|
|
case ADVERTISING_CAMPAIGN_PARK_ENTRY_FREE:
|
|
case ADVERTISING_CAMPAIGN_PARK_ENTRY_HALF_PRICE:
|
|
if (!Park::EntranceFeeUnlocked())
|
|
return false;
|
|
return true;
|
|
|
|
case ADVERTISING_CAMPAIGN_RIDE_FREE:
|
|
if (!Park::RidePricesUnlocked())
|
|
return false;
|
|
|
|
// fall-through
|
|
case ADVERTISING_CAMPAIGN_RIDE:
|
|
// Check if any rides exist
|
|
for (auto& ride : GetRideManager())
|
|
{
|
|
if (ride.IsRide())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case ADVERTISING_CAMPAIGN_FOOD_OR_DRINK_FREE:
|
|
// Check if any food or drink stalls exist
|
|
for (auto& ride : GetRideManager())
|
|
{
|
|
auto rideEntry = ride.GetRideEntry();
|
|
if (rideEntry != nullptr)
|
|
{
|
|
for (auto& item : rideEntry->shop_item)
|
|
{
|
|
if (GetShopItemDescriptor(item).IsFoodOrDrink())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
MarketingCampaign* MarketingGetCampaign(int32_t campaignType)
|
|
{
|
|
for (auto& campaign : gMarketingCampaigns)
|
|
{
|
|
if (campaign.Type == campaignType)
|
|
{
|
|
return &campaign;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void MarketingNewCampaign(const MarketingCampaign& campaign)
|
|
{
|
|
// Do not allow the same campaign twice, just overwrite
|
|
auto currentCampaign = MarketingGetCampaign(campaign.Type);
|
|
if (currentCampaign != nullptr)
|
|
{
|
|
*currentCampaign = campaign;
|
|
}
|
|
else
|
|
{
|
|
gMarketingCampaigns.push_back(campaign);
|
|
}
|
|
}
|
|
|
|
void MarketingCancelCampaignsForRide(const RideId rideId)
|
|
{
|
|
auto isCampaignForRideFn = [&rideId](MarketingCampaign& campaign) {
|
|
if (campaign.Type == ADVERTISING_CAMPAIGN_RIDE_FREE || campaign.Type == ADVERTISING_CAMPAIGN_RIDE)
|
|
{
|
|
return campaign.RideId == rideId;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
auto& v = gMarketingCampaigns;
|
|
auto removedIt = std::remove_if(v.begin(), v.end(), isCampaignForRideFn);
|
|
v.erase(removedIt, v.end());
|
|
}
|