1 /***************************************************************************** 2 * Copyright (c) 2014-2021 OpenRCT2 developers 3 * 4 * For a complete list of all authors, please refer to contributors.md 5 * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 6 * 7 * OpenRCT2 is licensed under the GNU General Public License version 3. 8 *****************************************************************************/ 9 10 #ifdef ENABLE_SCRIPTING 11 12 # include "ScTile.hpp" 13 14 # include "../../../Context.h" 15 # include "../../../common.h" 16 # include "../../../core/Guard.hpp" 17 # include "../../../ride/Track.h" 18 # include "../../../world/Footpath.h" 19 # include "../../../world/Scenery.h" 20 # include "../../../world/Sprite.h" 21 # include "../../../world/Surface.h" 22 # include "../../Duktape.hpp" 23 # include "../../ScriptEngine.h" 24 # include "ScTileElement.hpp" 25 26 # include <cstdio> 27 # include <cstring> 28 # include <utility> 29 30 namespace OpenRCT2::Scripting 31 { ScTile(const CoordsXY & coords)32 ScTile::ScTile(const CoordsXY& coords) 33 : _coords(coords) 34 { 35 } 36 x_get() const37 int32_t ScTile::x_get() const 38 { 39 return _coords.x / COORDS_XY_STEP; 40 } 41 y_get() const42 int32_t ScTile::y_get() const 43 { 44 return _coords.y / COORDS_XY_STEP; 45 } 46 numElements_get() const47 uint32_t ScTile::numElements_get() const 48 { 49 auto first = GetFirstElement(); 50 return static_cast<int32_t>(GetNumElements(first)); 51 } 52 elements_get() const53 std::vector<std::shared_ptr<ScTileElement>> ScTile::elements_get() const 54 { 55 std::vector<std::shared_ptr<ScTileElement>> result; 56 auto first = GetFirstElement(); 57 auto currentNumElements = GetNumElements(first); 58 if (currentNumElements != 0) 59 { 60 result.reserve(currentNumElements); 61 for (size_t i = 0; i < currentNumElements; i++) 62 { 63 result.push_back(std::make_shared<ScTileElement>(_coords, &first[i])); 64 } 65 } 66 return result; 67 } 68 data_get() const69 DukValue ScTile::data_get() const 70 { 71 auto ctx = GetDukContext(); 72 auto first = map_get_first_element_at(_coords); 73 auto dataLen = GetNumElements(first) * sizeof(TileElement); 74 auto data = duk_push_fixed_buffer(ctx, dataLen); 75 if (first != nullptr) 76 { 77 std::memcpy(data, first, dataLen); 78 } 79 duk_push_buffer_object(ctx, -1, 0, dataLen, DUK_BUFOBJ_UINT8ARRAY); 80 return DukValue::take_from_stack(ctx); 81 } 82 data_set(DukValue value)83 void ScTile::data_set(DukValue value) 84 { 85 ThrowIfGameStateNotMutable(); 86 auto ctx = value.context(); 87 value.push(); 88 if (duk_is_buffer_data(ctx, -1)) 89 { 90 duk_size_t dataLen{}; 91 auto data = duk_get_buffer_data(ctx, -1, &dataLen); 92 auto numElements = dataLen / sizeof(TileElement); 93 if (numElements == 0) 94 { 95 map_set_tile_element(TileCoordsXY(_coords), nullptr); 96 } 97 else 98 { 99 auto first = GetFirstElement(); 100 auto currentNumElements = GetNumElements(first); 101 if (numElements > currentNumElements) 102 { 103 // Allocate space for the extra tile elements (inefficient but works) 104 auto pos = TileCoordsXYZ(TileCoordsXY(_coords), 0).ToCoordsXYZ(); 105 auto numToInsert = numElements - currentNumElements; 106 for (size_t i = 0; i < numToInsert; i++) 107 { 108 tile_element_insert(pos, 0, TileElementType::Surface); 109 } 110 111 // Copy data to element span 112 first = map_get_first_element_at(_coords); 113 currentNumElements = GetNumElements(first); 114 if (currentNumElements != 0) 115 { 116 std::memcpy(first, data, currentNumElements * sizeof(TileElement)); 117 // Safely force last tile flag for last element to avoid read overrun 118 first[numElements - 1].SetLastForTile(true); 119 } 120 } 121 else 122 { 123 std::memcpy(first, data, numElements * sizeof(TileElement)); 124 // Safely force last tile flag for last element to avoid read overrun 125 first[numElements - 1].SetLastForTile(true); 126 } 127 } 128 map_invalidate_tile_full(_coords); 129 } 130 } 131 getElement(uint32_t index) const132 std::shared_ptr<ScTileElement> ScTile::getElement(uint32_t index) const 133 { 134 auto first = GetFirstElement(); 135 if (static_cast<size_t>(index) < GetNumElements(first)) 136 { 137 return std::make_shared<ScTileElement>(_coords, &first[index]); 138 } 139 return {}; 140 } 141 insertElement(uint32_t index)142 std::shared_ptr<ScTileElement> ScTile::insertElement(uint32_t index) 143 { 144 ThrowIfGameStateNotMutable(); 145 std::shared_ptr<ScTileElement> result; 146 auto first = GetFirstElement(); 147 auto origNumElements = GetNumElements(first); 148 if (index <= origNumElements) 149 { 150 std::vector<TileElement> data(first, first + origNumElements); 151 152 auto pos = TileCoordsXYZ(TileCoordsXY(_coords), 0).ToCoordsXYZ(); 153 auto newElement = tile_element_insert(pos, 0, TileElementType::Surface); 154 if (newElement == nullptr) 155 { 156 auto ctx = GetDukContext(); 157 duk_error(ctx, DUK_ERR_ERROR, "Unable to allocate element."); 158 } 159 else 160 { 161 // Inefficient, requires a dedicated method in tile element manager 162 first = GetFirstElement(); 163 // Copy elements before index 164 if (index > 0) 165 { 166 std::memcpy(first, &data[0], index * sizeof(TileElement)); 167 } 168 // Zero new element 169 std::memset(first + index, 0, sizeof(TileElement)); 170 // Copy elements after index 171 if (index < origNumElements) 172 { 173 std::memcpy(first + index + 1, &data[index], (origNumElements - index) * sizeof(TileElement)); 174 } 175 for (size_t i = 0; i < origNumElements; i++) 176 { 177 first[i].SetLastForTile(false); 178 } 179 first[origNumElements].SetLastForTile(true); 180 map_invalidate_tile_full(_coords); 181 result = std::make_shared<ScTileElement>(_coords, &first[index]); 182 } 183 } 184 else 185 { 186 auto ctx = GetDukContext(); 187 duk_error(ctx, DUK_ERR_RANGE_ERROR, "Index must be between zero and the number of elements on the tile."); 188 } 189 return result; 190 } 191 removeElement(uint32_t index)192 void ScTile::removeElement(uint32_t index) 193 { 194 ThrowIfGameStateNotMutable(); 195 auto first = GetFirstElement(); 196 if (index < GetNumElements(first)) 197 { 198 tile_element_remove(&first[index]); 199 map_invalidate_tile_full(_coords); 200 } 201 } 202 GetFirstElement() const203 TileElement* ScTile::GetFirstElement() const 204 { 205 return map_get_first_element_at(_coords); 206 } 207 GetNumElements(const TileElement * first)208 size_t ScTile::GetNumElements(const TileElement* first) 209 { 210 size_t count = 0; 211 if (first != nullptr) 212 { 213 auto element = first; 214 do 215 { 216 count++; 217 } while (!(element++)->IsLastForTile()); 218 } 219 return count; 220 } 221 GetDukContext() const222 duk_context* ScTile::GetDukContext() const 223 { 224 auto& scriptEngine = GetContext()->GetScriptEngine(); 225 auto ctx = scriptEngine.GetContext(); 226 return ctx; 227 } 228 Register(duk_context * ctx)229 void ScTile::Register(duk_context* ctx) 230 { 231 dukglue_register_property(ctx, &ScTile::x_get, nullptr, "x"); 232 dukglue_register_property(ctx, &ScTile::y_get, nullptr, "y"); 233 dukglue_register_property(ctx, &ScTile::elements_get, nullptr, "elements"); 234 dukglue_register_property(ctx, &ScTile::numElements_get, nullptr, "numElements"); 235 dukglue_register_property(ctx, &ScTile::data_get, &ScTile::data_set, "data"); 236 dukglue_register_method(ctx, &ScTile::getElement, "getElement"); 237 dukglue_register_method(ctx, &ScTile::insertElement, "insertElement"); 238 dukglue_register_method(ctx, &ScTile::removeElement, "removeElement"); 239 } 240 241 } // namespace OpenRCT2::Scripting 242 243 #endif 244