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