mirror of
https://github.com/OpenRCT2/OpenRCT2.git
synced 2026-05-06 07:56:46 -04:00
503 lines
14 KiB
C++
503 lines
14 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2026 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 "Banner.h"
|
|
|
|
#include "../Context.h"
|
|
#include "../Diagnostic.h"
|
|
#include "../Game.h"
|
|
#include "../GameState.h"
|
|
#include "../core/String.hpp"
|
|
#include "../localisation/Formatter.h"
|
|
#include "../localisation/Formatting.h"
|
|
#include "../management/Finance.h"
|
|
#include "../network/Network.h"
|
|
#include "../object/BannerSceneryEntry.h"
|
|
#include "../object/ObjectEntryManager.h"
|
|
#include "../object/WallSceneryEntry.h"
|
|
#include "../ride/Ride.h"
|
|
#include "../ride/RideData.h"
|
|
#include "../ride/RideManager.hpp"
|
|
#include "../windows/Intent.h"
|
|
#include "Map.h"
|
|
#include "MapAnimation.h"
|
|
#include "Park.h"
|
|
#include "Scenery.h"
|
|
#include "TileElementsView.h"
|
|
#include "tile_element/BannerElement.h"
|
|
#include "tile_element/TileElement.h"
|
|
#include "tile_element/TrackElement.h"
|
|
#include "tile_element/WallElement.h"
|
|
|
|
#include <cstring>
|
|
#include <iterator>
|
|
#include <limits>
|
|
|
|
using namespace OpenRCT2;
|
|
|
|
std::string Banner::getTextWithColour() const
|
|
{
|
|
Formatter ft;
|
|
formatTextWithColourTo(ft);
|
|
return FormatStringIDLegacy(STR_STRINGID, ft.Data());
|
|
}
|
|
|
|
std::string Banner::getText() const
|
|
{
|
|
Formatter ft;
|
|
formatTextTo(ft);
|
|
return FormatStringIDLegacy(STR_STRINGID, ft.Data());
|
|
}
|
|
|
|
void Banner::formatTextWithColourTo(Formatter& ft) const
|
|
{
|
|
// Use thread_local buffer to avoid race conditions during multithreaded rendering.
|
|
// Multiple threads can call this on the same Banner simultaneously when rendering
|
|
// different viewport columns in parallel.
|
|
thread_local std::string formattedTextBuffer;
|
|
|
|
auto formatToken = FormatTokenFromTextColour(textColour);
|
|
formattedTextBuffer = FormatTokenToStringWithBraces(formatToken);
|
|
ft.Add<StringId>(STR_STRING_STRINGID);
|
|
ft.Add<const char*>(formattedTextBuffer.data());
|
|
|
|
formatTextTo(ft);
|
|
}
|
|
|
|
void Banner::formatTextTo(Formatter& ft) const
|
|
{
|
|
if (flags.has(BannerFlag::noEntry))
|
|
{
|
|
ft.Add<StringId>(STR_NO_ENTRY);
|
|
}
|
|
else if (flags.has(BannerFlag::linkedToRide))
|
|
{
|
|
auto ride = GetRide(rideIndex);
|
|
if (ride != nullptr)
|
|
{
|
|
ride->formatNameTo(ft);
|
|
}
|
|
else
|
|
{
|
|
ft.Add<StringId>(STR_DEFAULT_SIGN);
|
|
}
|
|
}
|
|
else if (text.empty())
|
|
{
|
|
ft.Add<StringId>(STR_DEFAULT_SIGN);
|
|
}
|
|
else
|
|
{
|
|
ft.Add<StringId>(STR_STRING).Add<const char*>(text.c_str());
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006B7EAB
|
|
*/
|
|
static RideId BannerGetRideIndexAt(const CoordsXYZ& bannerCoords)
|
|
{
|
|
TileElement* tileElement = MapGetFirstElementAt(bannerCoords);
|
|
RideId resultRideIndex = RideId::GetNull();
|
|
if (tileElement == nullptr)
|
|
return resultRideIndex;
|
|
do
|
|
{
|
|
if (tileElement->GetType() != TileElementType::Track)
|
|
continue;
|
|
|
|
RideId rideIndex = tileElement->AsTrack()->GetRideIndex();
|
|
auto ride = GetRide(rideIndex);
|
|
if (ride == nullptr || ride->getRideTypeDescriptor().flags.has(RtdFlag::isShopOrFacility))
|
|
continue;
|
|
|
|
if ((tileElement->GetClearanceZ()) + (4 * kCoordsZStep) <= bannerCoords.z)
|
|
continue;
|
|
|
|
resultRideIndex = rideIndex;
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
|
|
return resultRideIndex;
|
|
}
|
|
|
|
static BannerIndex BannerGetNewIndex()
|
|
{
|
|
auto& gameState = getGameState();
|
|
for (BannerIndex::UnderlyingType bannerIndex = 0; bannerIndex < kMaxBanners; bannerIndex++)
|
|
{
|
|
if (bannerIndex < gameState.banners.size())
|
|
{
|
|
if (gameState.banners[bannerIndex].isNull())
|
|
{
|
|
return BannerIndex::FromUnderlying(bannerIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gameState.banners.emplace_back();
|
|
return BannerIndex::FromUnderlying(bannerIndex);
|
|
}
|
|
}
|
|
return BannerIndex::GetNull();
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006B9CB0
|
|
*/
|
|
void BannerInit(GameState_t& gameState)
|
|
{
|
|
gameState.banners.clear();
|
|
}
|
|
|
|
TileElement* BannerGetTileElement(BannerIndex bannerIndex)
|
|
{
|
|
auto banner = GetBanner(bannerIndex);
|
|
if (banner != nullptr)
|
|
{
|
|
auto tileElement = MapGetFirstElementAt(banner->position);
|
|
if (tileElement != nullptr)
|
|
{
|
|
do
|
|
{
|
|
if (tileElement->GetBannerIndex() == bannerIndex)
|
|
{
|
|
return tileElement;
|
|
}
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
WallElement* BannerGetScrollingWallTileElement(BannerIndex bannerIndex)
|
|
{
|
|
auto banner = GetBanner(bannerIndex);
|
|
if (banner == nullptr)
|
|
return nullptr;
|
|
|
|
auto tileElement = MapGetFirstElementAt(banner->position);
|
|
if (tileElement == nullptr)
|
|
return nullptr;
|
|
|
|
do
|
|
{
|
|
auto wallElement = tileElement->AsWall();
|
|
|
|
if (wallElement == nullptr)
|
|
continue;
|
|
|
|
auto* wallEntry = wallElement->GetEntry();
|
|
if (wallEntry->scrolling_mode == kScrollingModeNone)
|
|
continue;
|
|
if (wallElement->GetBannerIndex() != bannerIndex)
|
|
continue;
|
|
return wallElement;
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006B7D86
|
|
*/
|
|
RideId BannerGetClosestRideIndex(const CoordsXYZ& mapPos)
|
|
{
|
|
static constexpr std::array NeighbourCheckOrder = {
|
|
CoordsXY{ kCoordsXYStep, 0 },
|
|
CoordsXY{ -kCoordsXYStep, 0 },
|
|
CoordsXY{ 0, kCoordsXYStep },
|
|
CoordsXY{ 0, -kCoordsXYStep },
|
|
CoordsXY{ -kCoordsXYStep, +kCoordsXYStep },
|
|
CoordsXY{ +kCoordsXYStep, -kCoordsXYStep },
|
|
CoordsXY{ +kCoordsXYStep, +kCoordsXYStep },
|
|
CoordsXY{ -kCoordsXYStep, +kCoordsXYStep },
|
|
CoordsXY{ 0, 0 },
|
|
};
|
|
|
|
for (const auto& neighhbourCoords : NeighbourCheckOrder)
|
|
{
|
|
RideId rideIndex = BannerGetRideIndexAt({ CoordsXY{ mapPos } + neighhbourCoords, mapPos.z });
|
|
if (!rideIndex.IsNull())
|
|
{
|
|
return rideIndex;
|
|
}
|
|
}
|
|
|
|
auto rideIndex = RideId::GetNull();
|
|
auto resultDistance = std::numeric_limits<int32_t>::max();
|
|
|
|
auto& gameState = getGameState();
|
|
for (auto& ride : RideManager(gameState))
|
|
{
|
|
if (ride.getRideTypeDescriptor().flags.has(RtdFlag::isShopOrFacility))
|
|
continue;
|
|
|
|
auto rideCoords = ride.overallView;
|
|
if (rideCoords.IsNull())
|
|
continue;
|
|
|
|
int32_t distance = abs(mapPos.x - rideCoords.x) + abs(mapPos.y - rideCoords.y);
|
|
if (distance < resultDistance)
|
|
{
|
|
resultDistance = distance;
|
|
rideIndex = ride.id;
|
|
}
|
|
}
|
|
return rideIndex;
|
|
}
|
|
|
|
struct BannerElementWithPos
|
|
{
|
|
BannerElement* Element;
|
|
TileCoordsXY Pos;
|
|
};
|
|
|
|
// Returns a list of BannerElement's with the tile position.
|
|
static std::vector<BannerElementWithPos> GetAllBannerElementsOnMap()
|
|
{
|
|
auto& gameState = getGameState();
|
|
std::vector<BannerElementWithPos> banners;
|
|
for (int y = 0; y < gameState.mapSize.y; y++)
|
|
{
|
|
for (int x = 0; x < gameState.mapSize.x; x++)
|
|
{
|
|
const auto tilePos = TileCoordsXY{ x, y };
|
|
for (auto* bannerElement : OpenRCT2::TileElementsView<BannerElement>(tilePos.ToCoordsXY()))
|
|
{
|
|
auto bannerIndex = bannerElement->GetIndex();
|
|
if (bannerIndex == BannerIndex::GetNull())
|
|
continue;
|
|
|
|
banners.push_back({ bannerElement, tilePos });
|
|
}
|
|
}
|
|
}
|
|
return banners;
|
|
}
|
|
|
|
// Iterates all banners and checks if the tile specified by the position actually
|
|
// has a tile with the banner index, if no tile is found then the banner element will be released.
|
|
static void BannerDeallocateUnlinked()
|
|
{
|
|
auto& gameState = getGameState();
|
|
for (BannerIndex::UnderlyingType index = 0; index < gameState.banners.size(); index++)
|
|
{
|
|
const auto bannerId = BannerIndex::FromUnderlying(index);
|
|
auto* tileElement = BannerGetTileElement(bannerId);
|
|
if (tileElement == nullptr)
|
|
{
|
|
auto* banner = GetBanner(bannerId);
|
|
if (banner != nullptr)
|
|
{
|
|
banner->type = kBannerNull;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// BannerElement tiles should not share a banner entry, this iterates
|
|
// over all banner elements that shares the index and creates a new entry also
|
|
// copying the data from the current assigned banner entry.
|
|
static void BannerFixDuplicates(std::vector<BannerElementWithPos>& bannerElements)
|
|
{
|
|
// Sort the banners by index
|
|
std::sort(bannerElements.begin(), bannerElements.end(), [](const BannerElementWithPos& a, const BannerElementWithPos& b) {
|
|
return a.Element->GetIndex() < b.Element->GetIndex();
|
|
});
|
|
|
|
// Create a list of all banners with duplicate indices.
|
|
std::vector<BannerElementWithPos> duplicates;
|
|
for (size_t i = 1; i < bannerElements.size(); i++)
|
|
{
|
|
if (bannerElements[i - 1].Element->GetIndex() == bannerElements[i].Element->GetIndex())
|
|
{
|
|
duplicates.push_back(bannerElements[i]);
|
|
}
|
|
}
|
|
|
|
// For each duplicate, create a new banner and copy the old data
|
|
for (const auto& duplicate : duplicates)
|
|
{
|
|
const auto oldIndex = duplicate.Element->GetIndex();
|
|
const auto* oldBanner = GetBanner(oldIndex);
|
|
if (oldBanner == nullptr)
|
|
{
|
|
LOG_ERROR("Unable to get old banner for index %u.", oldIndex.ToUnderlying());
|
|
continue;
|
|
}
|
|
|
|
auto* newBanner = CreateBanner();
|
|
if (newBanner == nullptr)
|
|
{
|
|
LOG_ERROR("Failed to create new banner.");
|
|
continue;
|
|
}
|
|
|
|
const auto newBannerId = newBanner->id;
|
|
|
|
// Copy the old data to the new banner.
|
|
*newBanner = *oldBanner;
|
|
newBanner->id = newBannerId;
|
|
|
|
// Assign the new banner index to the tile element.
|
|
duplicate.Element->SetIndex(newBannerId);
|
|
}
|
|
}
|
|
|
|
// Ensures that all banner entries have the correct position based on the element
|
|
// that references the banner entry.
|
|
static void BannerFixPositions(std::vector<BannerElementWithPos>& bannerElements)
|
|
{
|
|
for (const auto& entry : bannerElements)
|
|
{
|
|
const auto index = entry.Element->GetIndex();
|
|
auto* banner = GetBanner(index);
|
|
if (banner == nullptr)
|
|
{
|
|
LOG_ERROR("Unable to get banner for index %u.", index.ToUnderlying());
|
|
continue;
|
|
}
|
|
banner->position = entry.Pos;
|
|
}
|
|
}
|
|
|
|
void BannerApplyFixes()
|
|
{
|
|
auto bannerElements = GetAllBannerElementsOnMap();
|
|
|
|
BannerFixDuplicates(bannerElements);
|
|
|
|
BannerFixPositions(bannerElements);
|
|
|
|
BannerDeallocateUnlinked();
|
|
}
|
|
|
|
void UnlinkAllRideBanners()
|
|
{
|
|
auto& gameState = getGameState();
|
|
for (auto& banner : gameState.banners)
|
|
{
|
|
if (!banner.isNull())
|
|
{
|
|
banner.flags.unset(BannerFlag::linkedToRide);
|
|
banner.rideIndex = RideId::GetNull();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UnlinkAllBannersForRide(RideId rideId)
|
|
{
|
|
auto& gameState = getGameState();
|
|
for (auto& banner : gameState.banners)
|
|
{
|
|
if (!banner.isNull() && banner.flags.has(BannerFlag::linkedToRide) && banner.rideIndex == rideId)
|
|
{
|
|
banner.flags.unset(BannerFlag::linkedToRide);
|
|
banner.rideIndex = RideId::GetNull();
|
|
banner.text = {};
|
|
}
|
|
}
|
|
}
|
|
|
|
Banner* GetBanner(BannerIndex id)
|
|
{
|
|
auto& gameState = getGameState();
|
|
const auto index = id.ToUnderlying();
|
|
if (index < gameState.banners.size())
|
|
{
|
|
auto banner = &gameState.banners[index];
|
|
if (banner != nullptr && !banner->isNull())
|
|
{
|
|
return banner;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Banner* GetOrCreateBanner(BannerIndex id)
|
|
{
|
|
auto& gameState = getGameState();
|
|
const auto index = id.ToUnderlying();
|
|
if (index < kMaxBanners)
|
|
{
|
|
if (index >= gameState.banners.size())
|
|
{
|
|
gameState.banners.resize(index + 1);
|
|
}
|
|
// Create the banner
|
|
auto& banner = gameState.banners[index];
|
|
banner.id = id;
|
|
return &banner;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Banner* CreateBanner()
|
|
{
|
|
auto bannerIndex = BannerGetNewIndex();
|
|
auto banner = GetOrCreateBanner(bannerIndex);
|
|
if (banner != nullptr)
|
|
{
|
|
banner->id = bannerIndex;
|
|
banner->flags = {};
|
|
banner->type = 0;
|
|
banner->text = {};
|
|
banner->colour = OpenRCT2::Drawing::Colour::white;
|
|
banner->textColour = Drawing::TextColour::white;
|
|
}
|
|
return banner;
|
|
}
|
|
|
|
void DeleteBanner(BannerIndex id)
|
|
{
|
|
auto* const banner = GetBanner(id);
|
|
if (banner != nullptr)
|
|
{
|
|
*banner = {};
|
|
}
|
|
}
|
|
|
|
void TrimBanners()
|
|
{
|
|
auto& gameState = getGameState();
|
|
if (!gameState.banners.empty())
|
|
{
|
|
auto lastBannerId = gameState.banners.size() - 1;
|
|
while (lastBannerId != std::numeric_limits<size_t>::max() && gameState.banners[lastBannerId].isNull())
|
|
{
|
|
lastBannerId--;
|
|
}
|
|
gameState.banners.resize(lastBannerId + 1);
|
|
gameState.banners.shrink_to_fit();
|
|
}
|
|
}
|
|
|
|
size_t GetNumBanners()
|
|
{
|
|
auto& gameState = getGameState();
|
|
size_t count = 0;
|
|
for (const auto& banner : gameState.banners)
|
|
{
|
|
if (!banner.isNull())
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
bool HasReachedBannerLimit()
|
|
{
|
|
auto numBanners = GetNumBanners();
|
|
return numBanners >= kMaxBanners;
|
|
}
|