Fix #5261 Deleting the sign after copy/pasting it will crash the game

Prior to the previous commit, signs could be duplicated by pasting them using the tile inspector, which results in some funky behaviour. A duplicated sign refers to the same banner index (which in turn refers to the same user string ID), making the player unable to modify it. Deleting it would previously even crash the game.
This commit looks for such signs when loading a scenario, and attempts to fix them, by creating a new banner entry (and user string if one is used).
This commit is contained in:
Hielke Morsink 2018-02-24 16:33:11 +01:00 committed by Hielke Morsink
parent d2479e0de0
commit 087519ca62
4 changed files with 69 additions and 0 deletions

View File

@ -54,6 +54,7 @@
- Fix: [#5190] Cannot build Wild Mouse - Flying Dutchman Gold Mine.
- Fix: [#5224] Multiplayer window is not closed when server shuts down.
- Fix: [#5228] Top toolbar disappears when opening SC4 file.
- Fix: [#5261] Deleting the sign after copy/pasting it will crash the game.
- Fix: [#5398] Attempting to place Mini Maze.TD4 results in weird behaviour and crashes.
- Fix: [#5417] Hacked Crooked House tracked rides do not dispatch vehicles.
- Fix: [#5445] Patrol area not imported from RCT1 saves and scenarios.

View File

@ -1297,6 +1297,9 @@ void game_fix_save_vars()
// Fix banner list pointing to NULL map elements
banner_reset_broken_index();
// Fix banners which share their index
fix_duplicated_banners();
// Fix invalid vehicle sprite sizes, thus preventing visual corruption of sprites
fix_invalid_vehicle_sprite_sizes();

View File

@ -547,6 +547,70 @@ void banner_reset_broken_index()
}
}
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[Util::CountOf(gBanners)]{};
rct_tile_element * 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 (tile_element_get_type(tileElement) == TILE_ELEMENT_TYPE_BANNER)
{
uint8 bannerIndex = tileElement->properties.banner.index;
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
uint8 newBannerIndex = create_new_banner(GAME_COMMAND_FLAG_APPLY);
if (newBannerIndex == BANNER_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->properties.banner.index = newBannerIndex;
}
// Mark banner index as in-use
activeBanners[bannerIndex] = true;
}
} while (!tile_element_is_last_for_tile(tileElement++));
}
}
}
/**
*
* rct2: 0x006BA058

View File

@ -50,6 +50,7 @@ sint32 create_new_banner(uint8 flags);
rct_tile_element *banner_get_tile_element(sint32 bannerIndex);
sint32 banner_get_closest_ride_index(sint32 x, sint32 y, sint32 z);
void banner_reset_broken_index();
void fix_duplicated_banners();
void game_command_callback_place_banner(sint32 eax, sint32 ebx, sint32 ecx, sint32 edx, sint32 esi, sint32 edi, sint32 ebp);
#endif