From 087519ca62b7c575f54eef59589a242636a96a00 Mon Sep 17 00:00:00 2001 From: Hielke Morsink Date: Sat, 24 Feb 2018 16:33:11 +0100 Subject: [PATCH] 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). --- distribution/changelog.txt | 1 + src/openrct2/Game.cpp | 3 ++ src/openrct2/world/Banner.cpp | 64 +++++++++++++++++++++++++++++++++++ src/openrct2/world/Banner.h | 1 + 4 files changed, 69 insertions(+) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 7a8b50a006..9189306379 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -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. diff --git a/src/openrct2/Game.cpp b/src/openrct2/Game.cpp index 805e62ecbe..d9b0ecc251 100644 --- a/src/openrct2/Game.cpp +++ b/src/openrct2/Game.cpp @@ -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(); diff --git a/src/openrct2/world/Banner.cpp b/src/openrct2/world/Banner.cpp index 4dcebfa2de..2c34243bdc 100644 --- a/src/openrct2/world/Banner.cpp +++ b/src/openrct2/world/Banner.cpp @@ -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 diff --git a/src/openrct2/world/Banner.h b/src/openrct2/world/Banner.h index 7d777dd3ad..9fe1370cef 100644 --- a/src/openrct2/world/Banner.h +++ b/src/openrct2/world/Banner.h @@ -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