diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 7d6baf2a22..e68e32010f 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -356,12 +356,15 @@ declare global { } type TileElementType = - "surface" | "footpath" | "track" | "small_scenery" | "wall" | "entrance" | "large_scenery" | "banner"; + "surface" | "footpath" | "track" | "small_scenery" | "wall" | "entrance" | "large_scenery" | "banner" + /** This only exist to retrieve the types for existing corrupt elements. For hiding elements, use the isHidden field instead. */ + | "openrct2_corrupt_deprecated"; interface BaseTileElement { type: TileElementType; baseHeight: number; clearanceHeight: number; + isHidden: boolean; /** Take caution when changing this field, it may invalidate TileElements you have stored in your script. */ } interface SurfaceElement extends BaseTileElement { diff --git a/src/openrct2/scripting/ScTile.hpp b/src/openrct2/scripting/ScTile.hpp index 620c6f8fee..48682d6a0d 100644 --- a/src/openrct2/scripting/ScTile.hpp +++ b/src/openrct2/scripting/ScTile.hpp @@ -13,6 +13,7 @@ # include "../Context.h" # include "../common.h" +# include "../core/Guard.hpp" # include "../world/Footpath.h" # include "../world/Scenery.h" # include "../world/Sprite.h" @@ -22,6 +23,7 @@ # include # include +# include namespace OpenRCT2::Scripting { @@ -62,7 +64,7 @@ namespace OpenRCT2::Scripting case TILE_ELEMENT_TYPE_BANNER: return "banner"; case TILE_ELEMENT_TYPE_CORRUPT: - return "openrct2_corrupt"; + return "openrct2_corrupt_deprecated"; default: return "unknown"; } @@ -87,10 +89,14 @@ namespace OpenRCT2::Scripting type = TILE_ELEMENT_TYPE_LARGE_SCENERY; else if (value == "banner") type = TILE_ELEMENT_TYPE_BANNER; - else if (value == "openrct2_corrupt") - type = TILE_ELEMENT_TYPE_CORRUPT; else + { + if (value == "openrct2_corrupt_deprecated") + std::puts( + "Creation of new corrupt elements is deprecated. To hide elements, use the 'hidden' property instead."); return; + } + _element->type = type; map_invalidate_tile_full(_coords); } @@ -491,6 +497,82 @@ namespace OpenRCT2::Scripting } } + bool isHidden_get() const + { + // TODO: Simply return the 'hidden' field once corrupt elements are superseded. + const TileElement* element = map_get_first_element_at(_coords); + bool previousElementWasUsefulCorrupt = false; + do + { + if (element == _element) + return previousElementWasUsefulCorrupt; + + if (element->GetType() == TILE_ELEMENT_TYPE_CORRUPT) + previousElementWasUsefulCorrupt = !previousElementWasUsefulCorrupt; + else + previousElementWasUsefulCorrupt = false; + } while (!(element++)->IsLastForTile()); + + Guard::Assert(false); + return false; + } + void isHidden_set(bool hide) + { + // TODO: Simply update the 'hidden' field once corrupt elements are superseded. + ThrowIfGameStateNotMutable(); + const bool isHidden = isHidden_get(); + if (hide == isHidden) + return; + + if (hide) + { + // Get index of our current element (has to be done now before inserting the corrupt element) + const auto elementIndex = _element - map_get_first_element_at(_coords); + + // Insert corrupt element at the end of the list for this tile + // Note: Z = MAX_ELEMENT_HEIGHT to guarantee this + TileElement* insertedElement = tile_element_insert({ _coords, MAX_ELEMENT_HEIGHT }, 0); + if (insertedElement == nullptr) + { + // TODO: Show error + return; + } + insertedElement->SetType(TILE_ELEMENT_TYPE_CORRUPT); + + // Since inserting a new element may move the tile elements in memory, we have to update the local pointer + _element = map_get_first_element_at(_coords) + elementIndex; + + // Move the corrupt element down in the list until it's right under our element + while (insertedElement > _element) + { + std::swap(*insertedElement, *(insertedElement - 1)); + insertedElement--; + + // Un-swap the last-for-tile flag + if (insertedElement->IsLastForTile()) + { + insertedElement->SetLastForTile(false); + (insertedElement + 1)->SetLastForTile(true); + } + } + + // Now the corrupt element took the hidden element's place, increment it by one + _element++; + + // Update base and clearance heights of inserted corrupt element to match the element to hide + insertedElement->base_height = insertedElement->clearance_height = _element->base_height; + } + else + { + TileElement* const elementToRemove = _element - 1; + Guard::Assert(elementToRemove->GetType() == TILE_ELEMENT_TYPE_CORRUPT); + tile_element_remove(elementToRemove); + _element--; + } + + map_invalidate_tile_full(_coords); + } + uint8_t age_get() const { auto el = _element->AsSmallScenery(); @@ -830,6 +912,7 @@ namespace OpenRCT2::Scripting // Some dukglue_register_property(ctx, &ScTileElement::object_get, &ScTileElement::object_set, "object"); + dukglue_register_property(ctx, &ScTileElement::isHidden_get, &ScTileElement::isHidden_set, "isHidden"); // Small Scenery | Large Scenery dukglue_register_property( diff --git a/src/openrct2/world/TileElement.h b/src/openrct2/world/TileElement.h index 2b8de73e88..c5c0324c6f 100644 --- a/src/openrct2/world/TileElement.h +++ b/src/openrct2/world/TileElement.h @@ -21,6 +21,8 @@ struct rct_scenery_entry; struct rct_footpath_entry; using track_type_t = uint16_t; +constexpr const uint8_t MAX_ELEMENT_HEIGHT = 255; + #pragma pack(push, 1) enum diff --git a/src/openrct2/world/TileInspector.cpp b/src/openrct2/world/TileInspector.cpp index 0e309e0768..7b30204588 100644 --- a/src/openrct2/world/TileInspector.cpp +++ b/src/openrct2/world/TileInspector.cpp @@ -421,7 +421,8 @@ GameActionResult::Ptr tile_inspector_any_base_height_offset( int16_t newBaseHeight = static_cast(tileElement->base_height + heightOffset); int16_t newClearanceHeight = static_cast(tileElement->clearance_height + heightOffset); - if (newBaseHeight < 0 || newBaseHeight > 0xff || newClearanceHeight < 0 || newClearanceHeight > 0xff) + if (newBaseHeight < 0 || newBaseHeight > MAX_ELEMENT_HEIGHT || newClearanceHeight < 0 + || newClearanceHeight > MAX_ELEMENT_HEIGHT) { return std::make_unique(GA_ERROR::UNKNOWN, STR_NONE); }