diff --git a/distribution/openrct2.d.ts b/distribution/openrct2.d.ts index 998f3bb311..585e5dbd28 100644 --- a/distribution/openrct2.d.ts +++ b/distribution/openrct2.d.ts @@ -242,12 +242,34 @@ declare global { interface CorruptElement extends BaseTileElement { } + /** + * Represents a tile containing tile elements on the map. This is a fixed handle + * for a given tile position. It can be re-used safely between game ticks. + */ interface Tile { + /** The x position in tiles. */ readonly x: number; + /** The y position in tiles. */ readonly y: number; - elements: TileElement[]; + /** Gets an array of all the tile elements on this tile. */ + readonly elements: TileElement[]; + /** Gets the number of tile elements on this tile. */ + readonly numElements: number; + /** + * Gets or sets the raw data for this tile. + * This can provide more control and efficiency for tile manipulation but requires + * knowledge of tile element structures and may change between versions of OpenRCT2. + */ + data: Uint8Array; + /** Gets the tile element at the given index on this tile. */ getElement(index: number): TileElement; + /** Gets the tile element at the given index on this tile. */ + getElement(index: number): T; + /** Inserts a new tile element at the given index on this tile. */ + insertElement(index: number): TileElement; + /** Removes the tile element at the given index from this tile. */ + removeElement(index: number): void; } interface Object { diff --git a/src/openrct2/scripting/ScMap.hpp b/src/openrct2/scripting/ScMap.hpp index 94f4749a3b..191821d416 100644 --- a/src/openrct2/scripting/ScMap.hpp +++ b/src/openrct2/scripting/ScMap.hpp @@ -70,8 +70,7 @@ namespace OpenRCT2::Scripting std::shared_ptr getTile(int32_t x, int32_t y) { auto coords = TileCoordsXY(x, y).ToCoordsXY(); - auto firstElement = map_get_first_element_at(coords); - return std::make_shared(coords, firstElement); + return std::make_shared(coords); } std::shared_ptr getThing(int32_t id) diff --git a/src/openrct2/scripting/ScTile.hpp b/src/openrct2/scripting/ScTile.hpp index e8d4fd6315..2f72c4f04c 100644 --- a/src/openrct2/scripting/ScTile.hpp +++ b/src/openrct2/scripting/ScTile.hpp @@ -66,6 +66,33 @@ namespace OpenRCT2::Scripting } } + void type_set(std::string value) + { + auto type = _element->type; + if (value == "surface") + type = TILE_ELEMENT_TYPE_SURFACE; + else if (value == "footpath") + type = TILE_ELEMENT_TYPE_PATH; + else if (value == "track") + type = TILE_ELEMENT_TYPE_TRACK; + else if (value == "small-scenery") + type = TILE_ELEMENT_TYPE_SMALL_SCENERY; + else if (value == "entrance") + type = TILE_ELEMENT_TYPE_ENTRANCE; + else if (value == "wall") + type = TILE_ELEMENT_TYPE_WALL; + else if (value == "large-scenery") + 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 + return; + _element->type = type; + map_invalidate_tile_full(_coords); + } + bool broken_get() const { return _element->flags & TILE_ELEMENT_FLAG_BROKEN; @@ -132,11 +159,12 @@ namespace OpenRCT2::Scripting public: static void Register(duk_context* ctx) { - dukglue_register_property(ctx, &ScTileElement::type_get, nullptr, "type"); - dukglue_register_property(ctx, &ScTileElement::broken_get, &ScTileElement::broken_set, "broken"); + dukglue_register_property(ctx, &ScTileElement::type_get, &ScTileElement::type_set, "type"); dukglue_register_property(ctx, &ScTileElement::baseHeight_get, &ScTileElement::baseHeight_set, "baseHeight"); dukglue_register_property( ctx, &ScTileElement::clearanceHeight_get, &ScTileElement::clearanceHeight_set, "clearanceHeight"); + + dukglue_register_property(ctx, &ScTileElement::broken_get, &ScTileElement::broken_set, "broken"); dukglue_register_property(ctx, &ScTileElement::grassLength_get, &ScTileElement::grassLength_set, "grassLength"); dukglue_register_property(ctx, &ScTileElement::hasOwnership_get, nullptr, "hasOwnership"); } @@ -146,25 +174,14 @@ namespace OpenRCT2::Scripting { private: CoordsXY _coords; - TileElement* _first; - size_t _count = 0; public: - ScTile(CoordsXY coords, TileElement* first) + ScTile(CoordsXY coords) : _coords(coords) - , _first(first) { - _count = 0; - if (first != nullptr) - { - auto element = first; - do - { - _count++; - } while (!(element++)->IsLastForTile()); - } } + private: int32_t x_get() { return _coords.x / 32; @@ -175,24 +192,194 @@ namespace OpenRCT2::Scripting return _coords.y / 32; } - size_t elements_get() + size_t numElements_get() { - return _count; + auto first = GetFirstElement(); + return GetNumElements(first); + } + + std::vector> elements_get() + { + std::vector> result; + auto first = GetFirstElement(); + auto currentNumElements = GetNumElements(first); + if (currentNumElements != 0) + { + result.reserve(currentNumElements); + for (size_t i = 0; i < currentNumElements; i++) + { + result.push_back(std::make_shared(_coords, &first[i])); + } + } + return result; + } + + DukValue data_get() + { + auto ctx = GetDukContext(); + auto first = map_get_first_element_at(_coords); + auto dataLen = GetNumElements(first) * sizeof(TileElement); + auto data = duk_push_fixed_buffer(ctx, dataLen); + if (first != nullptr) + { + std::memcpy(data, first, dataLen); + } + duk_push_buffer_object(ctx, -1, 0, dataLen, DUK_BUFOBJ_UINT8ARRAY); + return DukValue::take_from_stack(ctx); + } + + void data_set(DukValue value) + { + auto ctx = value.context(); + value.push(); + if (duk_is_buffer_data(ctx, -1)) + { + duk_size_t dataLen{}; + auto data = duk_get_buffer_data(ctx, -1, &dataLen); + auto numElements = dataLen / sizeof(TileElement); + if (numElements == 0) + { + map_set_tile_element(TileCoordsXY(_coords), nullptr); + } + else + { + auto first = GetFirstElement(); + auto currentNumElements = GetNumElements(first); + if (numElements > currentNumElements) + { + // Allocate space for the extra tile elements (inefficient but works) + auto pos = TileCoordsXYZ(TileCoordsXY(_coords), 0); + auto numToInsert = numElements - currentNumElements; + for (size_t i = 0; i < numToInsert; i++) + { + tile_element_insert(pos, 0); + } + + // Copy data to element span + first = map_get_first_element_at(_coords); + currentNumElements = GetNumElements(first); + if (currentNumElements != 0) + { + std::memcpy(first, data, currentNumElements * sizeof(TileElement)); + // Safely force last tile flag for last element to avoid read overrun + first[numElements - 1].SetLastForTile(true); + } + } + else + { + std::memcpy(first, data, numElements * sizeof(TileElement)); + // Safely force last tile flag for last element to avoid read overrun + first[numElements - 1].SetLastForTile(true); + } + } + map_invalidate_tile_full(_coords); + } } std::shared_ptr getElement(size_t index) { - if (index >= _count) - return nullptr; - return std::make_shared(_coords, &_first[index]); + auto first = GetFirstElement(); + if (index < GetNumElements(first)) + { + return std::make_shared(_coords, &first[index]); + } + return {}; } + std::shared_ptr insertElement(size_t index) + { + auto first = GetFirstElement(); + auto origNumElements = GetNumElements(first); + if (index >= 0 && index <= origNumElements) + { + std::vector data(first, first + origNumElements); + + auto pos = TileCoordsXYZ(TileCoordsXY(_coords), 0); + auto newElement = tile_element_insert(pos, 0); + if (newElement == nullptr) + { + auto ctx = GetDukContext(); + duk_error(ctx, DUK_ERR_ERROR, "Unable to allocate element."); + } + else + { + // Inefficient, requires a dedicated method in tile element manager + first = GetFirstElement(); + // Copy elements before index + if (index > 0) + { + std::memcpy(first, &data[0], index * sizeof(TileElement)); + } + // Zero new element + std::memset(first + index, 0, sizeof(TileElement)); + // Copy elements after index + if (index < origNumElements) + { + std::memcpy(first + index + 1, &data[index], (origNumElements - index) * sizeof(TileElement)); + } + for (size_t i = 0; i < origNumElements; i++) + { + first[i].SetLastForTile(false); + } + first[origNumElements].SetLastForTile(true); + map_invalidate_tile_full(_coords); + return std::make_shared(_coords, &first[index]); + } + } + else + { + auto ctx = GetDukContext(); + duk_error(ctx, DUK_ERR_RANGE_ERROR, "Index must be between zero and the number of elements on the tile."); + } + } + + void removeElement(size_t index) + { + auto first = GetFirstElement(); + if (index < GetNumElements(first)) + { + tile_element_remove(&first[index]); + map_invalidate_tile_full(_coords); + } + } + + TileElement* GetFirstElement() + { + return map_get_first_element_at(_coords); + } + + static size_t GetNumElements(const TileElement* first) + { + size_t count = 0; + if (first != nullptr) + { + auto element = first; + do + { + count++; + } while (!(element++)->IsLastForTile()); + } + return count; + } + + duk_context* GetDukContext() + { + auto& scriptEngine = GetContext()->GetScriptEngine(); + auto ctx = scriptEngine.GetContext(); + return ctx; + } + + public: static void Register(duk_context* ctx) { dukglue_register_property(ctx, &ScTile::x_get, nullptr, "x"); dukglue_register_property(ctx, &ScTile::y_get, nullptr, "y"); dukglue_register_property(ctx, &ScTile::elements_get, nullptr, "elements"); + dukglue_register_property(ctx, &ScTile::numElements_get, nullptr, "numElements"); + dukglue_register_property(ctx, &ScTile::data_get, &ScTile::data_set, "data"); dukglue_register_method(ctx, &ScTile::getElement, "getElement"); + dukglue_register_method(ctx, &ScTile::insertElement, "insertElement"); + dukglue_register_method(ctx, &ScTile::removeElement, "removeElement"); } }; } // namespace OpenRCT2::Scripting diff --git a/src/openrct2/world/Map.cpp b/src/openrct2/world/Map.cpp index 07519cbc33..2937cec6ef 100644 --- a/src/openrct2/world/Map.cpp +++ b/src/openrct2/world/Map.cpp @@ -1170,21 +1170,28 @@ TileElement* tile_element_insert(const CoordsXYZ& loc, int32_t occupiedQuadrants // Set tile index pointer to point to new element block gTileElementTilePointers[tileLoc.y * MAXIMUM_MAP_SIZE_TECHNICAL + tileLoc.x] = newTileElement; - // Copy all elements that are below the insert height - while (loc.z >= originalTileElement->GetBaseZ()) + if (originalTileElement == nullptr) { - // Copy over map element - *newTileElement = *originalTileElement; - originalTileElement->base_height = 255; - originalTileElement++; - newTileElement++; - - if ((newTileElement - 1)->IsLastForTile()) + isLastForTile = true; + } + else + { + // Copy all elements that are below the insert height + while (loc.z >= originalTileElement->GetBaseZ()) { - // No more elements above the insert element - (newTileElement - 1)->SetLastForTile(false); - isLastForTile = true; - break; + // Copy over map element + *newTileElement = *originalTileElement; + originalTileElement->base_height = 255; + originalTileElement++; + newTileElement++; + + if ((newTileElement - 1)->IsLastForTile()) + { + // No more elements above the insert element + (newTileElement - 1)->SetLastForTile(false); + isLastForTile = true; + break; + } } }