mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-23 23:04:36 +01:00
477 lines
14 KiB
C++
477 lines
14 KiB
C++
/*****************************************************************************
|
|
* 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 <algorithm>
|
|
#include <cstring>
|
|
#include <iterator>
|
|
#include <limits>
|
|
|
|
rct_banner gBanners[MAX_BANNERS];
|
|
|
|
/**
|
|
*
|
|
* 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;
|
|
do
|
|
{
|
|
if (tileElement->GetType() != TILE_ELEMENT_TYPE_TRACK)
|
|
continue;
|
|
|
|
ride_id_t rideIndex = tileElement->AsTrack()->GetRideIndex();
|
|
Ride* ride = get_ride(rideIndex);
|
|
if (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 money32 BannerRemove(int16_t x, int16_t y, uint8_t baseHeight, uint8_t direction, uint8_t flags)
|
|
{
|
|
int32_t z = baseHeight * 8;
|
|
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
|
|
gCommandPosition.x = x + 16;
|
|
gCommandPosition.y = y + 16;
|
|
gCommandPosition.z = z;
|
|
|
|
if (!(flags & GAME_COMMAND_FLAG_GHOST) && game_is_paused() && !gCheatsBuildInPauseMode)
|
|
{
|
|
gGameCommandErrorText = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED;
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
if (!map_can_build_at(x, y, z - 16))
|
|
{
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
// Slight modification to the code so that it now checks height as well
|
|
// This was causing a bug with banners on two paths stacked.
|
|
BannerElement* tileElement = map_get_banner_element_at(x / 32, y / 32, baseHeight, direction);
|
|
if (tileElement == nullptr)
|
|
{
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
rct_banner* banner = &gBanners[tileElement->GetIndex()];
|
|
rct_scenery_entry* bannerEntry = get_banner_entry(banner->type);
|
|
money32 refund = 0;
|
|
if (bannerEntry != nullptr)
|
|
{
|
|
refund = -((bannerEntry->banner.price * 3) / 4);
|
|
}
|
|
|
|
if (flags & GAME_COMMAND_FLAG_APPLY)
|
|
{
|
|
if (gGameCommandNestLevel == 1 && !(flags & GAME_COMMAND_FLAG_GHOST))
|
|
{
|
|
LocationXYZ16 coord;
|
|
coord.x = x + 16;
|
|
coord.y = y + 16;
|
|
coord.z = tile_element_height(coord.x, coord.y);
|
|
network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord);
|
|
}
|
|
|
|
tile_element_remove_banner_entry((TileElement*)tileElement);
|
|
map_invalidate_tile_zoom1(x, y, z, z + 32);
|
|
tileElement->Remove();
|
|
}
|
|
|
|
if (gParkFlags & PARK_FLAGS_NO_MONEY)
|
|
{
|
|
refund = 0;
|
|
}
|
|
return refund;
|
|
}
|
|
|
|
static money32 BannerPlace(
|
|
int16_t x, int16_t y, uint8_t pathBaseHeight, uint8_t direction, uint8_t colour, uint8_t type, BannerIndex* bannerIndex,
|
|
uint8_t flags)
|
|
{
|
|
gCommandPosition.x = x + 16;
|
|
gCommandPosition.y = y + 16;
|
|
gCommandPosition.z = pathBaseHeight * 16;
|
|
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
|
|
if (game_is_paused() && !gCheatsBuildInPauseMode)
|
|
{
|
|
gGameCommandErrorText = STR_CONSTRUCTION_NOT_POSSIBLE_WHILE_GAME_IS_PAUSED;
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
if (!map_check_free_elements_and_reorganise(1))
|
|
{
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
if (!map_is_location_valid({ x, y }))
|
|
{
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
TileElement* tileElement = map_get_first_element_at(x / 32, y / 32);
|
|
|
|
bool pathFound = false;
|
|
do
|
|
{
|
|
if (tileElement->GetType() != TILE_ELEMENT_TYPE_PATH)
|
|
continue;
|
|
|
|
if (tileElement->base_height != pathBaseHeight * 2 && tileElement->base_height != (pathBaseHeight - 1) * 2)
|
|
continue;
|
|
|
|
if (!(tileElement->AsPath()->GetEdges() & (1 << direction)))
|
|
continue;
|
|
|
|
pathFound = true;
|
|
break;
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
|
|
if (!pathFound)
|
|
{
|
|
gGameCommandErrorText = STR_CAN_ONLY_BE_BUILT_ACROSS_PATHS;
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
if (!map_can_build_at(x, y, pathBaseHeight * 16))
|
|
{
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
uint8_t baseHeight = (pathBaseHeight + 1) * 2;
|
|
BannerElement* bannerElement = map_get_banner_element_at(x / 32, y / 32, baseHeight, direction);
|
|
if (bannerElement != nullptr)
|
|
{
|
|
gGameCommandErrorText = STR_BANNER_SIGN_IN_THE_WAY;
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
*bannerIndex = create_new_banner(flags);
|
|
if (*bannerIndex == BANNER_INDEX_NULL)
|
|
{
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
if (flags & GAME_COMMAND_FLAG_APPLY)
|
|
{
|
|
if (gGameCommandNestLevel == 1 && !(flags & GAME_COMMAND_FLAG_GHOST))
|
|
{
|
|
LocationXYZ16 coord;
|
|
coord.x = x + 16;
|
|
coord.y = y + 16;
|
|
coord.z = tile_element_height(coord.x, coord.y);
|
|
network_set_player_last_action_coord(network_get_player_index(game_command_playerid), coord);
|
|
}
|
|
|
|
TileElement* newTileElement = tile_element_insert(x / 32, y / 32, baseHeight, 0);
|
|
assert(newTileElement != nullptr);
|
|
gBanners[*bannerIndex].type = type;
|
|
gBanners[*bannerIndex].colour = colour;
|
|
gBanners[*bannerIndex].x = x / 32;
|
|
gBanners[*bannerIndex].y = y / 32;
|
|
newTileElement->SetType(TILE_ELEMENT_TYPE_BANNER);
|
|
newTileElement->clearance_height = newTileElement->base_height + 2;
|
|
newTileElement->AsBanner()->SetPosition(direction);
|
|
newTileElement->AsBanner()->ResetAllowedEdges();
|
|
newTileElement->AsBanner()->SetIndex(*bannerIndex);
|
|
if (flags & GAME_COMMAND_FLAG_GHOST)
|
|
{
|
|
newTileElement->SetGhost(true);
|
|
}
|
|
map_invalidate_tile_full(x, y);
|
|
map_animation_create(MAP_ANIMATION_TYPE_BANNER, x, y, newTileElement->base_height);
|
|
}
|
|
|
|
rct_scenery_entry* bannerEntry = get_banner_entry(type);
|
|
if (bannerEntry == nullptr)
|
|
{
|
|
return MONEY32_UNDEFINED;
|
|
}
|
|
|
|
if (gParkFlags & PARK_FLAGS_NO_MONEY)
|
|
{
|
|
return 0;
|
|
}
|
|
return bannerEntry->banner.price;
|
|
}
|
|
|
|
static BannerIndex BannerGetNewIndex()
|
|
{
|
|
for (BannerIndex bannerIndex = 0; bannerIndex < MAX_BANNERS; bannerIndex++)
|
|
{
|
|
if (gBanners[bannerIndex].type == BANNER_NULL)
|
|
{
|
|
return bannerIndex;
|
|
}
|
|
}
|
|
return BANNER_INDEX_NULL;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006B9CB0
|
|
*/
|
|
void banner_init()
|
|
{
|
|
for (auto& banner : gBanners)
|
|
{
|
|
banner.type = BANNER_NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
rct_banner* banner = &gBanners[bannerIndex];
|
|
|
|
banner->flags = 0;
|
|
banner->type = 0;
|
|
banner->string_idx = STR_DEFAULT_SIGN;
|
|
banner->colour = 2;
|
|
banner->text_colour = 2;
|
|
}
|
|
return bannerIndex;
|
|
}
|
|
|
|
TileElement* banner_get_tile_element(BannerIndex bannerIndex)
|
|
{
|
|
rct_banner* banner = &gBanners[bannerIndex];
|
|
TileElement* tileElement = map_get_first_element_at(banner->x, banner->y);
|
|
do
|
|
{
|
|
if (tile_element_get_banner_index(tileElement) == bannerIndex)
|
|
{
|
|
return tileElement;
|
|
}
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006B7D86
|
|
*/
|
|
uint8_t banner_get_closest_ride_index(int32_t x, int32_t y, int32_t z)
|
|
{
|
|
Ride* ride;
|
|
|
|
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 < (int32_t)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;
|
|
}
|
|
}
|
|
|
|
uint8_t index;
|
|
ride_id_t rideIndex = RIDE_ID_NULL;
|
|
int32_t resultDistance = std::numeric_limits<int32_t>::max();
|
|
FOR_ALL_RIDES (index, ride)
|
|
{
|
|
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 = index;
|
|
}
|
|
}
|
|
|
|
return rideIndex;
|
|
}
|
|
|
|
void banner_reset_broken_index()
|
|
{
|
|
for (BannerIndex bannerIndex = 0; bannerIndex < MAX_BANNERS; bannerIndex++)
|
|
{
|
|
TileElement* tileElement = banner_get_tile_element(bannerIndex);
|
|
if (tileElement == nullptr)
|
|
gBanners[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(gBanners)]{};
|
|
TileElement* tileElement;
|
|
for (int y = 0; y < MAXIMUM_MAP_SIZE_TECHNICAL; y++)
|
|
{
|
|
for (int x = 0; x < MAXIMUM_MAP_SIZE_TECHNICAL; x++)
|
|
{
|
|
tileElement = map_get_first_element_at(x, y);
|
|
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] == false);
|
|
|
|
// Copy over the original banner, but update the location
|
|
rct_banner& newBanner = gBanners[newBannerIndex];
|
|
newBanner = gBanners[bannerIndex];
|
|
newBanner.x = x;
|
|
newBanner.y = y;
|
|
|
|
// Duplicate user string too
|
|
rct_string_id stringIdx = newBanner.string_idx;
|
|
if (is_user_string_id(stringIdx))
|
|
{
|
|
utf8 buffer[USER_STRING_MAX_LENGTH];
|
|
format_string(buffer, USER_STRING_MAX_LENGTH, stringIdx, nullptr);
|
|
rct_string_id newStringIdx = user_string_allocate(USER_STRING_DUPLICATION_PERMITTED, buffer);
|
|
if (newStringIdx == 0)
|
|
{
|
|
log_error("Failed to allocate user string for banner");
|
|
continue;
|
|
}
|
|
newBanner.string_idx = newStringIdx;
|
|
}
|
|
|
|
tileElement->AsBanner()->SetIndex(newBannerIndex);
|
|
}
|
|
|
|
// Mark banner index as in-use
|
|
activeBanners[bannerIndex] = true;
|
|
}
|
|
} while (!(tileElement++)->IsLastForTile());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006BA058
|
|
*/
|
|
void game_command_remove_banner(
|
|
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, [[maybe_unused]] int32_t* esi, [[maybe_unused]] int32_t* edi,
|
|
[[maybe_unused]] int32_t* ebp)
|
|
{
|
|
*ebx = BannerRemove(*eax & 0xFFFF, *ecx & 0xFFFF, *edx & 0xFF, (*edx >> 8) & 0xFF, *ebx & 0xFF);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006B9E6D
|
|
*/
|
|
void game_command_place_banner(
|
|
int32_t* eax, int32_t* ebx, int32_t* ecx, int32_t* edx, [[maybe_unused]] int32_t* esi, int32_t* edi, int32_t* ebp)
|
|
{
|
|
*ebx = BannerPlace(
|
|
*eax & 0xFFFF, *ecx & 0xFFFF, *edx & 0xFF, (*edx >> 8) & 0xFF, *ebp & 0xFF, (*ebx >> 8) & 0xFF, (BannerIndex*)edi,
|
|
*ebx & 0xFF);
|
|
}
|
|
|
|
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;
|
|
}
|