1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-28 09:14:58 +01:00
Files
OpenRCT2/src/openrct2/world/Banner.cpp
2018-02-05 16:20:15 +01:00

622 lines
17 KiB
C++

#pragma region Copyright (c) 2014-2017 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include <algorithm>
#include <cstring>
#include <limits>
#include "../core/Math.hpp"
#include "../core/Memory.hpp"
#include "../core/Util.hpp"
#include "../core/String.hpp"
#include "../network/network.h"
#include "Banner.h"
#include "Map.h"
#include "Park.h"
#include "Scenery.h"
#include "../Game.h"
#include "../interface/Window.h"
#include "../localisation/Localisation.h"
#include "../ride/Ride.h"
#include "../ride/Track.h"
#include "../windows/Intent.h"
#include "../Context.h"
rct_banner gBanners[MAX_BANNERS];
/**
*
* rct2: 0x006B7EAB
*/
static sint32 banner_get_ride_index_at(sint32 x, sint32 y, sint32 z)
{
rct_tile_element *tileElement;
Ride *ride;
sint32 rideIndex, resultRideIndex;
resultRideIndex = -1;
tileElement = map_get_first_element_at(x >> 5, y >> 5);
do
{
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_TRACK)
continue;
rideIndex = track_element_get_ride_index(tileElement);
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 (!tile_element_is_last_for_tile(tileElement++));
return resultRideIndex;
}
static money32 BannerRemove(sint16 x, sint16 y, uint8 baseHeight, uint8 direction, uint8 flags)
{
sint32 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.
rct_tile_element* tileElement = map_get_banner_element_at(x / 32, y / 32, baseHeight, direction);
if (tileElement == nullptr)
{
return MONEY32_UNDEFINED;
}
rct_banner *banner = &gBanners[tileElement->properties.banner.index];
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);
map_invalidate_tile_zoom1(x, y, z, z + 32);
tile_element_remove(tileElement);
}
if (gParkFlags & PARK_FLAGS_NO_MONEY)
{
refund = 0;
}
return refund;
}
static money32 BannerSetColour(sint16 x, sint16 y, uint8 baseHeight, uint8 direction, uint8 colour, uint8 flags)
{
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_LANDSCAPING;
sint32 z = (baseHeight * 8);
gCommandPosition.x = x + 16;
gCommandPosition.y = y + 16;
gCommandPosition.z = z;
if (!map_can_build_at(x, y, z - 16))
{
return MONEY32_UNDEFINED;
}
if (flags & GAME_COMMAND_FLAG_APPLY)
{
rct_tile_element* tileElement = map_get_first_element_at(x / 32, y / 32);
bool found = false;
do
{
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_BANNER)
continue;
if (tileElement->properties.banner.position != direction)
continue;
found = true;
break;
} while (!tile_element_is_last_for_tile(tileElement++));
if (!found)
{
return MONEY32_UNDEFINED;
}
auto intent = Intent(INTENT_ACTION_UPDATE_BANNER);
intent.putExtra(INTENT_EXTRA_BANNER_INDEX, tileElement->properties.banner.index);
context_broadcast_intent(&intent);
gBanners[tileElement->properties.banner.index].colour = colour;
map_invalidate_tile_zoom1(x, y, z, z + 32);
}
return 0;
}
static money32 BannerPlace(sint16 x, sint16 y, uint8 pathBaseHeight, uint8 direction, uint8 colour, uint8 type, uint8 *bannerIndex, uint8 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;
}
rct_tile_element* tileElement = map_get_first_element_at(x / 32, y / 32);
bool pathFound = false;
do
{
if (tile_element_get_type(tileElement) != TILE_ELEMENT_TYPE_PATH)
continue;
if (tileElement->base_height != pathBaseHeight * 2 && tileElement->base_height != (pathBaseHeight - 1) * 2)
continue;
if (!(tileElement->properties.path.edges & (1 << direction)))
continue;
pathFound = true;
break;
} while (!tile_element_is_last_for_tile(tileElement++));
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 baseHeight = (pathBaseHeight + 1) * 2;
tileElement = map_get_banner_element_at(x / 32, y / 32, baseHeight, direction);
if (tileElement != nullptr)
{
gGameCommandErrorText = STR_BANNER_SIGN_IN_THE_WAY;
return MONEY32_UNDEFINED;
}
*bannerIndex = create_new_banner(flags);
if (*bannerIndex == BANNER_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);
}
rct_tile_element* 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->type = TILE_ELEMENT_TYPE_BANNER;
newTileElement->clearance_height = newTileElement->base_height + 2;
newTileElement->properties.banner.position = direction;
newTileElement->properties.banner.flags = 0xFF;
newTileElement->properties.banner.unused = 0;
newTileElement->properties.banner.index = *bannerIndex;
if (flags & GAME_COMMAND_FLAG_GHOST)
{
newTileElement->flags |= TILE_ELEMENT_FLAG_GHOST;
}
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 money32 BannerSetName(uint8 bannerIndex,
uint16 nameChunkIndex,
uint32 nameChunk1,
uint32 nameChunk2,
uint32 nameChunk3,
uint8 flags)
{
static char newName[128];
if (bannerIndex >= MAX_BANNERS)
{
log_warning("Invalid game command for setting banner name, banner id = %d", bannerIndex);
return MONEY32_UNDEFINED;
}
rct_banner* banner = &gBanners[bannerIndex];
gCommandExpenditureType = RCT_EXPENDITURE_TYPE_RIDE_RUNNING_COSTS;
size_t indexToOffset[3] = { 24, 0, 12 };
if (nameChunkIndex > Util::CountOf(indexToOffset))
{
log_warning("Invalid chunk index for setting banner name, banner id = %d, index = %d", bannerIndex, nameChunkIndex);
return MONEY32_UNDEFINED;
}
size_t nameChunkOffset = std::min<size_t>(indexToOffset[nameChunkIndex], Util::CountOf(newName) - 12);
std::memcpy(&newName[0 + nameChunkOffset], &nameChunk1, sizeof(uint32));
std::memcpy(&newName[4 + nameChunkOffset], &nameChunk2, sizeof(uint32));
std::memcpy(&newName[8 + nameChunkOffset], &nameChunk3, sizeof(uint32));
if (nameChunkIndex != 0)
{
return 0;
}
if (!(flags & GAME_COMMAND_FLAG_APPLY))
{
return 0;
}
utf8 *buffer = gCommonStringFormatBuffer;
utf8 *dst = buffer;
dst = utf8_write_codepoint(dst, FORMAT_COLOUR_CODE_START + banner->text_colour);
String::Set(dst, 256, newName, 32);
rct_string_id stringId = user_string_allocate(USER_STRING_DUPLICATION_PERMITTED, buffer);
if (stringId != 0)
{
rct_string_id prevStringId = banner->string_idx;
banner->string_idx = stringId;
user_string_free(prevStringId);
auto intent = Intent(INTENT_ACTION_UPDATE_BANNER);
intent.putExtra(INTENT_EXTRA_BANNER_INDEX, bannerIndex);
context_broadcast_intent(&intent);
}
else
{
gGameCommandErrorText = STR_ERR_CANT_SET_BANNER_TEXT;
return MONEY32_UNDEFINED;
}
return 0;
}
static money32 BannerSetStyle(uint8 bannerIndex, uint8 colour, uint8 textColour, uint8 bannerFlags, uint8 flags)
{
if (bannerIndex >= MAX_BANNERS)
{
gGameCommandErrorText = STR_INVALID_SELECTION_OF_OBJECTS;
return MONEY32_UNDEFINED;
}
rct_banner* banner = &gBanners[bannerIndex];
rct_tile_element* tileElement = banner_get_tile_element(bannerIndex);
if (tileElement == nullptr)
{
return MONEY32_UNDEFINED;
}
if (!(flags & GAME_COMMAND_FLAG_APPLY))
{
return 0;
}
banner->colour = colour;
banner->text_colour = textColour;
banner->flags = bannerFlags;
tileElement->properties.banner.flags = 0xFF;
if (banner->flags & BANNER_FLAG_NO_ENTRY)
{
tileElement->properties.banner.flags &= ~(1 << tileElement->properties.banner.position);
}
sint32 colourCodepoint = FORMAT_COLOUR_CODE_START + banner->text_colour;
utf8 buffer[256];
format_string(buffer, 256, banner->string_idx, nullptr);
sint32 firstCodepoint = utf8_get_next(buffer, nullptr);
if (firstCodepoint >= FORMAT_COLOUR_CODE_START && firstCodepoint <= FORMAT_COLOUR_CODE_END)
{
utf8_write_codepoint(buffer, colourCodepoint);
}
else
{
utf8_insert_codepoint(buffer, colourCodepoint);
}
rct_string_id stringId = user_string_allocate(USER_STRING_DUPLICATION_PERMITTED, buffer);
if (stringId != 0)
{
rct_string_id prevStringId = banner->string_idx;
banner->string_idx = stringId;
user_string_free(prevStringId);
auto intent = Intent(INTENT_ACTION_UPDATE_BANNER);
intent.putExtra(INTENT_EXTRA_BANNER_INDEX, bannerIndex);
context_broadcast_intent(&intent);
}
else
{
gGameCommandErrorText = STR_ERR_CANT_SET_BANNER_TEXT;
return MONEY32_UNDEFINED;
}
return 0;
}
static uint8 BannerGetNewIndex() {
uint8 bannerIndex = 0;
for (; bannerIndex < MAX_BANNERS; bannerIndex++)
{
if (gBanners[bannerIndex].type == BANNER_NULL)
{
return bannerIndex;
}
}
return BANNER_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
*/
sint32 create_new_banner(uint8 flags)
{
uint8 bannerIndex = BannerGetNewIndex();
if (bannerIndex == BANNER_NULL)
{
gGameCommandErrorText = STR_TOO_MANY_BANNERS_IN_GAME;
return BANNER_NULL;
}
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;
}
rct_tile_element *banner_get_tile_element(sint32 bannerIndex)
{
rct_banner *banner = &gBanners[bannerIndex];
rct_tile_element *tileElement = map_get_first_element_at(banner->x, banner->y);
do
{
if (tile_element_get_banner_index(tileElement) == bannerIndex)
{
return tileElement;
}
} while (!tile_element_is_last_for_tile(tileElement++));
return nullptr;
}
/**
*
* rct2: 0x006B7D86
*/
sint32 banner_get_closest_ride_index(sint32 x, sint32 y, sint32 z)
{
sint32 i, rideIndex;
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 (i = 0; i < (sint32)Util::CountOf(NeighbourCheckOrder); i++)
{
rideIndex = banner_get_ride_index_at(x + NeighbourCheckOrder[i].x, y + NeighbourCheckOrder[i].y, z);
if (rideIndex != -1)
{
return rideIndex;
}
}
rideIndex = -1;
sint32 resultDistance = std::numeric_limits<sint32>::max();
FOR_ALL_RIDES(i, 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;
sint32 rideX = location.x * 32;
sint32 rideY = location.y * 32;
sint32 distance = abs(x - rideX) + abs(y - rideY);
if (distance < resultDistance)
{
resultDistance = distance;
rideIndex = i;
}
}
return rideIndex;
}
void banner_reset_broken_index()
{
for (sint32 bannerIndex = 0; bannerIndex < MAX_BANNERS; bannerIndex++)
{
rct_tile_element *tileElement = banner_get_tile_element(bannerIndex);
if (tileElement == nullptr)
gBanners[bannerIndex].type = BANNER_NULL;
}
}
/**
*
* rct2: 0x006BA058
*/
void game_command_remove_banner(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
*ebx = BannerRemove(
*eax & 0xFFFF,
*ecx & 0xFFFF,
*edx & 0xFF,
(*edx >> 8) & 0xFF,
*ebx & 0xFF
);
}
/**
*
* rct2: 0x006BA16A
*/
void game_command_set_banner_colour(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
*ebx = BannerSetColour(
*eax & 0xFFFF,
*ecx & 0xFFFF,
*edx & 0xFF,
(*edx >> 8) & 0xFF,
*ebp & 0xFF,
*ebx & 0xFF
);
}
/**
*
* rct2: 0x006B9E6D
*/
void game_command_place_banner(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
*ebx = BannerPlace(
*eax & 0xFFFF,
*ecx & 0xFFFF,
*edx & 0xFF,
(*edx >> 8) & 0xFF,
*ebp & 0xFF,
(*ebx >> 8) & 0xFF,
(uint8 *)edi,
*ebx & 0xFF
);
}
void game_command_set_banner_name(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
*ebx = BannerSetName(
*ecx & 0xFF,
*eax & 0xFFFF,
*edx,
*ebp,
*edi,
*ebx & 0xFF
);
}
void game_command_set_banner_style(sint32* eax, sint32* ebx, sint32* ecx, sint32* edx, sint32* esi, sint32* edi, sint32* ebp)
{
*ebx = BannerSetStyle(
*ecx & 0xFF,
*edx & 0xFF,
*edi & 0xFF,
*ebp & 0xFF,
*ebx & 0xFF
);
}