/***************************************************************************** * Copyright (c) 2014-2019 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 "../Game.h" #include "../core/Memory.hpp" #include "../core/String.hpp" #include "../interface/Window.h" #include "../localisation/Localisation.h" #include "../management/Finance.h" #include "../network/network.h" #include "../ride/Ride.h" #include "../ride/Track.h" #include "../windows/Intent.h" #include "Map.h" #include "MapAnimation.h" #include "Park.h" #include "Scenery.h" #include #include #include #include static Banner _banners[MAX_BANNERS]; std::string Banner::GetText() const { uint8_t args[32]{}; FormatTextTo(args); return format_string(STR_STRINGID, args); } size_t Banner::FormatTextTo(void* argsV) const { auto args = (uint8_t*)argsV; if (flags & BANNER_FLAG_NO_ENTRY) { set_format_arg_on(args, 0, rct_string_id, STR_NO_ENTRY); return sizeof(rct_string_id); } else if (flags & BANNER_FLAG_LINKED_TO_RIDE) { auto ride = get_ride(ride_index); if (ride != nullptr) { return ride->FormatNameTo(args); } else { set_format_arg_on(args, 0, rct_string_id, STR_DEFAULT_SIGN); return sizeof(rct_string_id); } } else if (text.empty()) { set_format_arg_on(args, 0, rct_string_id, STR_DEFAULT_SIGN); return sizeof(rct_string_id); } else { set_format_arg_on(args, 0, rct_string_id, STR_STRING); set_format_arg_on(args, 2, const char*, text.c_str()); return sizeof(rct_string_id) + sizeof(const char*); } } /** * * rct2: 0x006B7EAB */ static uint8_t banner_get_ride_index_at(int32_t x, int32_t y, int32_t z) { TileElement* tileElement = map_get_first_element_at(x >> 5, y >> 5); ride_id_t resultRideIndex = RIDE_ID_NULL; if (tileElement == nullptr) return resultRideIndex; do { if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK) continue; ride_id_t rideIndex = tileElement->AsTrack()->GetRideIndex(); auto ride = get_ride(rideIndex); if (ride == nullptr || ride_type_has_flag(ride->type, RIDE_TYPE_FLAG_IS_SHOP)) continue; if ((tileElement->clearance_height * 8) + 32 <= z) continue; resultRideIndex = rideIndex; } while (!(tileElement++)->IsLastForTile()); return resultRideIndex; } static BannerIndex BannerGetNewIndex() { for (BannerIndex bannerIndex = 0; bannerIndex < MAX_BANNERS; bannerIndex++) { if (_banners[bannerIndex].IsNull()) { return bannerIndex; } } return BANNER_INDEX_NULL; } /** * * rct2: 0x006B9CB0 */ void banner_init() { for (auto& banner : _banners) { banner = {}; } } /** * Creates a new banner and returns the index of the banner * If the flag GAME_COMMAND_FLAG_APPLY is NOT set then returns * the first unused index but does NOT mark the banner as created. * returns 0xFF on failure. * * rct2: 0x006BA278 */ BannerIndex create_new_banner(uint8_t flags) { BannerIndex bannerIndex = BannerGetNewIndex(); if (bannerIndex == BANNER_INDEX_NULL) { gGameCommandErrorText = STR_TOO_MANY_BANNERS_IN_GAME; return bannerIndex; } if (flags & GAME_COMMAND_FLAG_APPLY) { auto banner = &_banners[bannerIndex]; banner->flags = 0; banner->type = 0; banner->text = {}; banner->colour = 2; banner->text_colour = 2; } return bannerIndex; } TileElement* banner_get_tile_element(BannerIndex bannerIndex) { auto banner = GetBanner(bannerIndex); if (banner != nullptr) { auto tileElement = map_get_first_element_at(banner->position.x, banner->position.y); if (tileElement != nullptr) { do { if (tile_element_get_banner_index(tileElement) == bannerIndex) { return tileElement; } } while (!(tileElement++)->IsLastForTile()); } } return nullptr; } WallElement* banner_get_scrolling_wall_tile_element(BannerIndex bannerIndex) { auto banner = GetBanner(bannerIndex); if (banner == nullptr) return nullptr; auto tileElement = map_get_first_element_at(banner->position.x, banner->position.y); if (tileElement == nullptr) return nullptr; do { auto wallElement = tileElement->AsWall(); if (wallElement == nullptr) continue; rct_scenery_entry* scenery_entry = wallElement->GetEntry(); if (scenery_entry->wall.scrolling_mode == SCROLLING_MODE_NONE) continue; if (wallElement->GetBannerIndex() != bannerIndex) continue; return wallElement; } while (!(tileElement++)->IsLastForTile()); return nullptr; } /** * * rct2: 0x006B7D86 */ uint8_t banner_get_closest_ride_index(int32_t x, int32_t y, int32_t z) { static constexpr const LocationXY16 NeighbourCheckOrder[] = { { 32, 0 }, { -32, 0 }, { 0, 32 }, { 0, -32 }, { -32, +32 }, { +32, -32 }, { +32, +32 }, { -32, +32 }, { 0, 0 } }; for (size_t i = 0; i < std::size(NeighbourCheckOrder); i++) { ride_id_t rideIndex = banner_get_ride_index_at(x + NeighbourCheckOrder[i].x, y + NeighbourCheckOrder[i].y, z); if (rideIndex != RIDE_ID_NULL) { return rideIndex; } } auto rideIndex = RIDE_ID_NULL; auto resultDistance = std::numeric_limits::max(); for (auto& ride : GetRideManager()) { if (ride_type_has_flag(ride.type, RIDE_TYPE_FLAG_IS_SHOP)) continue; LocationXY8 location = ride.overall_view; if (location.xy == RCT_XY8_UNDEFINED) continue; int32_t rideX = location.x * 32; int32_t rideY = location.y * 32; int32_t distance = abs(x - rideX) + abs(y - rideY); if (distance < resultDistance) { resultDistance = distance; rideIndex = ride.id; } } return rideIndex; } void banner_reset_broken_index() { for (BannerIndex bannerIndex = 0; bannerIndex < MAX_BANNERS; bannerIndex++) { auto tileElement = banner_get_tile_element(bannerIndex); if (tileElement == nullptr) { _banners[bannerIndex].type = BANNER_NULL; } } } void fix_duplicated_banners() { // For each banner in the map, check if the banner index is in use already, and if so, create a new entry for it bool activeBanners[std::size(_banners)]{}; for (int y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++) { for (int x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++) { auto tileElement = map_get_first_element_at(x, y); if (tileElement != nullptr) { do { // TODO: Handle walls and large-scenery that use banner indices too. Large scenery can be tricky, as they // occupy multiple tiles that should both refer to the same banner index. if (tileElement->GetType() == TILE_ELEMENT_TYPE_BANNER) { uint8_t bannerIndex = tileElement->AsBanner()->GetIndex(); if (activeBanners[bannerIndex]) { log_info( "Duplicated banner with index %d found at x = %d, y = %d and z = %d.", bannerIndex, x, y, tileElement->base_height); // Banner index is already in use by another banner, so duplicate it BannerIndex newBannerIndex = create_new_banner(GAME_COMMAND_FLAG_APPLY); if (newBannerIndex == BANNER_INDEX_NULL) { log_error("Failed to create new banner."); continue; } Guard::Assert(!activeBanners[newBannerIndex]); // Copy over the original banner, but update the location auto& newBanner = *GetBanner(newBannerIndex); newBanner = *GetBanner(bannerIndex); newBanner.position = { x, y }; tileElement->AsBanner()->SetIndex(newBannerIndex); } // Mark banner index as in-use activeBanners[bannerIndex] = true; } } while (!(tileElement++)->IsLastForTile()); } } } } Banner* BannerElement::GetBanner() const { return ::GetBanner(GetIndex()); } rct_scenery_entry* BannerElement::GetEntry() const { auto banner = GetBanner(); if (banner != nullptr) { return get_banner_entry(banner->type); } return nullptr; } BannerIndex BannerElement::GetIndex() const { return index; } void BannerElement::SetIndex(BannerIndex newIndex) { index = newIndex; } uint8_t BannerElement::GetPosition() const { return position; } void BannerElement::SetPosition(uint8_t newPosition) { position = newPosition; } uint8_t BannerElement::GetAllowedEdges() const { return flags & 0b00001111; } void BannerElement::SetAllowedEdges(uint8_t newEdges) { flags &= ~0b00001111; flags |= (newEdges & 0b00001111); } void BannerElement::ResetAllowedEdges() { flags |= 0b00001111; } Banner* GetBanner(BannerIndex id) { if (id < std::size(_banners)) { return &_banners[id]; } return nullptr; }