1 /*****************************************************************************
2  * Copyright (c) 2014-2020 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 #pragma once
11 
12 #ifdef ENABLE_SCRIPTING
13 
14 #    include "../core/Console.hpp"
15 #    include "../core/EnumMap.hpp"
16 #    include "../ride/Vehicle.h"
17 #    include "../world/Map.h"
18 
19 #    include <cstdio>
20 #    include <dukglue/dukglue.h>
21 #    include <duktape.h>
22 #    include <optional>
23 #    include <stdexcept>
24 
25 namespace OpenRCT2::Scripting
26 {
GetObjectAsDukValue(duk_context * ctx,const std::shared_ptr<T> & value)27     template<typename T> DukValue GetObjectAsDukValue(duk_context* ctx, const std::shared_ptr<T>& value)
28     {
29         dukglue::types::DukType<std::shared_ptr<T>>::template push<T>(ctx, value);
30         return DukValue::take_from_stack(ctx);
31     }
32 
33     template<typename T> T AsOrDefault(const DukValue& value, const T& defaultValue = {}) = delete;
34 
AsOrDefault(const DukValue & value,std::string_view defaultValue)35     inline std::string AsOrDefault(const DukValue& value, std::string_view defaultValue)
36     {
37         return value.type() == DukValue::STRING ? value.as_string() : std::string(defaultValue);
38     }
39 
AsOrDefault(const DukValue & value,const std::string & defaultValue)40     template<> inline std::string AsOrDefault(const DukValue& value, const std::string& defaultValue)
41     {
42         return value.type() == DukValue::STRING ? value.as_string() : defaultValue;
43     }
44 
AsOrDefault(const DukValue & value,const int32_t & defaultValue)45     template<> inline int32_t AsOrDefault(const DukValue& value, const int32_t& defaultValue)
46     {
47         return value.type() == DukValue::NUMBER ? value.as_int() : defaultValue;
48     }
49 
AsOrDefault(const DukValue & value,const bool & defaultValue)50     template<> inline bool AsOrDefault(const DukValue& value, const bool& defaultValue)
51     {
52         return value.type() == DukValue::BOOLEAN ? value.as_bool() : defaultValue;
53     }
54 
55     enum class DukUndefined
56     {
57     };
58     constexpr DukUndefined undefined{};
59 
60     /**
61      * Allows creation of an object on the duktape stack and setting properties on it before
62      * retrieving the DukValue instance of it.
63      */
64     class DukObject
65     {
66     private:
67         duk_context* _ctx{};
68         duk_idx_t _idx = DUK_INVALID_INDEX;
69 
70     public:
DukObject(duk_context * ctx)71         DukObject(duk_context* ctx)
72             : _ctx(ctx)
73         {
74         }
75 
76         DukObject(const DukObject&) = delete;
77 
DukObject(DukObject && m)78         DukObject(DukObject&& m) noexcept
79         {
80             _ctx = m._ctx;
81             _idx = m._idx;
82             m._ctx = {};
83             m._idx = {};
84         }
85 
~DukObject()86         ~DukObject()
87         {
88             PopObjectIfExists();
89         }
90 
Set(const char * name,std::nullptr_t)91         void Set(const char* name, std::nullptr_t)
92         {
93             EnsureObjectPushed();
94             duk_push_null(_ctx);
95             duk_put_prop_string(_ctx, _idx, name);
96         }
97 
Set(const char * name,DukUndefined)98         void Set(const char* name, DukUndefined)
99         {
100             EnsureObjectPushed();
101             duk_push_undefined(_ctx);
102             duk_put_prop_string(_ctx, _idx, name);
103         }
104 
Set(const char * name,bool value)105         void Set(const char* name, bool value)
106         {
107             EnsureObjectPushed();
108             duk_push_boolean(_ctx, value);
109             duk_put_prop_string(_ctx, _idx, name);
110         }
111 
Set(const char * name,int32_t value)112         void Set(const char* name, int32_t value)
113         {
114             EnsureObjectPushed();
115             duk_push_int(_ctx, value);
116             duk_put_prop_string(_ctx, _idx, name);
117         }
118 
Set(const char * name,uint32_t value)119         void Set(const char* name, uint32_t value)
120         {
121             EnsureObjectPushed();
122             duk_push_uint(_ctx, value);
123             duk_put_prop_string(_ctx, _idx, name);
124         }
125 
Set(const char * name,int64_t value)126         void Set(const char* name, int64_t value)
127         {
128             EnsureObjectPushed();
129             duk_push_number(_ctx, value);
130             duk_put_prop_string(_ctx, _idx, name);
131         }
132 
Set(const char * name,uint64_t value)133         void Set(const char* name, uint64_t value)
134         {
135             EnsureObjectPushed();
136             duk_push_number(_ctx, value);
137             duk_put_prop_string(_ctx, _idx, name);
138         }
139 
Set(const char * name,std::string_view value)140         void Set(const char* name, std::string_view value)
141         {
142             EnsureObjectPushed();
143             duk_push_lstring(_ctx, value.data(), value.size());
144             duk_put_prop_string(_ctx, _idx, name);
145         }
146 
Set(const char * name,const char * value)147         void Set(const char* name, const char* value)
148         {
149             Set(name, std::string_view(value));
150         }
151 
Set(const char * name,const DukValue & value)152         void Set(const char* name, const DukValue& value)
153         {
154             EnsureObjectPushed();
155             value.push();
156             duk_put_prop_string(_ctx, _idx, name);
157         }
158 
Set(const char * name,const std::optional<T> & value)159         template<typename T> void Set(const char* name, const std::optional<T>& value)
160         {
161             if (value)
162             {
163                 EnsureObjectPushed();
164                 duk_push_null(_ctx);
165                 duk_put_prop_string(_ctx, _idx, name);
166             }
167             else
168             {
169                 Set(name, *value);
170             }
171         }
172 
Take()173         DukValue Take()
174         {
175             EnsureObjectPushed();
176             auto result = DukValue::take_from_stack(_ctx, _idx);
177             _idx = DUK_INVALID_INDEX;
178             return result;
179         }
180 
181     private:
PopObjectIfExists()182         void PopObjectIfExists()
183         {
184             if (_idx != DUK_INVALID_INDEX)
185             {
186                 duk_remove(_ctx, _idx);
187                 _idx = DUK_INVALID_INDEX;
188             }
189         }
190 
EnsureObjectPushed()191         void EnsureObjectPushed()
192         {
193             if (_idx == DUK_INVALID_INDEX)
194             {
195                 _idx = duk_push_object(_ctx);
196             }
197         }
198     };
199 
200     class DukStackFrame
201     {
202     private:
203         duk_context* _ctx{};
204         duk_idx_t _top;
205 
206     public:
DukStackFrame(duk_context * ctx)207         DukStackFrame(duk_context* ctx)
208             : _ctx(ctx)
209         {
210             _top = duk_get_top(ctx);
211         }
212 
~DukStackFrame()213         ~DukStackFrame()
214         {
215             auto top = duk_get_top(_ctx);
216             if (top != _top)
217             {
218                 duk_set_top(_ctx, _top);
219                 _ctx = {};
220                 Console::Error::WriteLine("duktape stack was not returned to original state!");
221             }
222             _ctx = {};
223         }
224 
225         DukStackFrame(const DukStackFrame&) = delete;
226         DukStackFrame(DukStackFrame&&) = delete;
227     };
228 
229     /**
230      * Bi-directional map for converting between strings and enums / numbers.
231      */
232     template<typename T> using DukEnumMap = EnumMap<T>;
233 
duk_json_decode_wrapper(duk_context * ctx,void *)234     inline duk_ret_t duk_json_decode_wrapper(duk_context* ctx, void*)
235     {
236         duk_json_decode(ctx, -1);
237         return 1;
238     }
239 
DuktapeTryParseJson(duk_context * ctx,std::string_view json)240     inline std::optional<DukValue> DuktapeTryParseJson(duk_context* ctx, std::string_view json)
241     {
242         duk_push_lstring(ctx, json.data(), json.size());
243         if (duk_safe_call(ctx, duk_json_decode_wrapper, nullptr, 1, 1) == DUK_EXEC_SUCCESS)
244         {
245             return DukValue::take_from_stack(ctx);
246         }
247 
248         // Pop error off stack
249         duk_pop(ctx);
250         return std::nullopt;
251     }
252 
253     std::string ProcessString(const DukValue& value);
254 
255     template<typename T> DukValue ToDuk(duk_context* ctx, const T& value) = delete;
256     template<typename T> T FromDuk(const DukValue& s) = delete;
257 
ToDuk(duk_context * ctx,const std::nullptr_t &)258     template<> inline DukValue ToDuk(duk_context* ctx, const std::nullptr_t&)
259     {
260         duk_push_null(ctx);
261         return DukValue::take_from_stack(ctx);
262     }
263 
ToDuk(duk_context * ctx,const DukUndefined &)264     template<> inline DukValue ToDuk(duk_context* ctx, const DukUndefined&)
265     {
266         duk_push_undefined(ctx);
267         return DukValue::take_from_stack(ctx);
268     }
269 
ToDuk(duk_context * ctx,const bool & value)270     template<> inline DukValue ToDuk(duk_context* ctx, const bool& value)
271     {
272         duk_push_boolean(ctx, value);
273         return DukValue::take_from_stack(ctx);
274     }
275 
ToDuk(duk_context * ctx,const uint8_t & value)276     template<> inline DukValue ToDuk(duk_context* ctx, const uint8_t& value)
277     {
278         duk_push_int(ctx, value);
279         return DukValue::take_from_stack(ctx);
280     }
281 
ToDuk(duk_context * ctx,const int32_t & value)282     template<> inline DukValue ToDuk(duk_context* ctx, const int32_t& value)
283     {
284         duk_push_int(ctx, value);
285         return DukValue::take_from_stack(ctx);
286     }
287 
ToDuk(duk_context * ctx,const int64_t & value)288     template<> inline DukValue ToDuk(duk_context* ctx, const int64_t& value)
289     {
290         duk_push_number(ctx, value);
291         return DukValue::take_from_stack(ctx);
292     }
293 
ToDuk(duk_context * ctx,const std::string_view & value)294     template<> inline DukValue ToDuk(duk_context* ctx, const std::string_view& value)
295     {
296         duk_push_lstring(ctx, value.data(), value.size());
297         return DukValue::take_from_stack(ctx);
298     }
299 
ToDuk(duk_context * ctx,const std::string & value)300     template<> inline DukValue ToDuk(duk_context* ctx, const std::string& value)
301     {
302         return ToDuk(ctx, std::string_view(value));
303     }
304 
ToDuk(duk_context * ctx,const char (& value)[TLen])305     template<size_t TLen> inline DukValue ToDuk(duk_context* ctx, const char (&value)[TLen])
306     {
307         duk_push_string(ctx, value);
308         return DukValue::take_from_stack(ctx);
309     }
310 
ToDuk(duk_context * ctx,const std::optional<T> & value)311     template<typename T> inline DukValue ToDuk(duk_context* ctx, const std::optional<T>& value)
312     {
313         return value ? ToDuk(ctx, *value) : ToDuk(ctx, nullptr);
314     }
315 
FromDuk(const DukValue & d)316     template<> CoordsXY inline FromDuk(const DukValue& d)
317     {
318         CoordsXY result;
319         result.x = AsOrDefault(d["x"], 0);
320         result.y = AsOrDefault(d["y"], 0);
321         return result;
322     }
323 
ToDuk(duk_context * ctx,const CoordsXY & coords)324     template<> DukValue inline ToDuk(duk_context* ctx, const CoordsXY& coords)
325     {
326         DukObject dukCoords(ctx);
327         dukCoords.Set("x", coords.x);
328         dukCoords.Set("y", coords.y);
329         return dukCoords.Take();
330     }
331 
ToDuk(duk_context * ctx,const ScreenCoordsXY & coords)332     template<> DukValue inline ToDuk(duk_context* ctx, const ScreenCoordsXY& coords)
333     {
334         DukObject dukCoords(ctx);
335         dukCoords.Set("x", coords.x);
336         dukCoords.Set("y", coords.y);
337         return dukCoords.Take();
338     }
339 
ToDuk(duk_context * ctx,const CoordsXYZ & value)340     template<> inline DukValue ToDuk(duk_context* ctx, const CoordsXYZ& value)
341     {
342         if (value.IsNull())
343         {
344             return ToDuk(ctx, nullptr);
345         }
346 
347         DukObject dukCoords(ctx);
348         dukCoords.Set("x", value.x);
349         dukCoords.Set("y", value.y);
350         dukCoords.Set("z", value.z);
351         return dukCoords.Take();
352     }
353 
FromDuk(const DukValue & value)354     template<> inline CoordsXYZ FromDuk(const DukValue& value)
355     {
356         CoordsXYZ result;
357         if (value.type() == DukValue::Type::OBJECT)
358         {
359             result.x = AsOrDefault(value["x"], 0);
360             result.y = AsOrDefault(value["y"], 0);
361             result.z = AsOrDefault(value["z"], 0);
362         }
363         else
364         {
365             result.SetNull();
366         }
367         return result;
368     }
369 
ToDuk(duk_context * ctx,const CoordsXYZD & value)370     template<> inline DukValue ToDuk(duk_context* ctx, const CoordsXYZD& value)
371     {
372         if (value.IsNull())
373         {
374             return ToDuk(ctx, nullptr);
375         }
376 
377         DukObject dukCoords(ctx);
378         dukCoords.Set("x", value.x);
379         dukCoords.Set("y", value.y);
380         dukCoords.Set("z", value.z);
381         dukCoords.Set("direction", value.direction);
382         return dukCoords.Take();
383     }
384 
ToDuk(duk_context * ctx,const GForces & value)385     template<> inline DukValue ToDuk(duk_context* ctx, const GForces& value)
386     {
387         DukObject dukGForces(ctx);
388         dukGForces.Set("lateralG", value.LateralG);
389         dukGForces.Set("verticalG", value.VerticalG);
390         return dukGForces.Take();
391     }
392 
FromDuk(const DukValue & value)393     template<> inline CoordsXYZD FromDuk(const DukValue& value)
394     {
395         CoordsXYZD result;
396         if (value.type() == DukValue::Type::OBJECT)
397         {
398             result.x = AsOrDefault(value["x"], 0);
399             result.y = AsOrDefault(value["y"], 0);
400             result.z = AsOrDefault(value["z"], 0);
401             result.direction = AsOrDefault(value["direction"], 0);
402         }
403         else
404         {
405             result.SetNull();
406         }
407         return result;
408     }
409 
ToDuk(duk_context * ctx,const ScreenSize & value)410     template<> inline DukValue ToDuk(duk_context* ctx, const ScreenSize& value)
411     {
412         DukObject dukCoords(ctx);
413         dukCoords.Set("width", value.width);
414         dukCoords.Set("height", value.height);
415         return dukCoords.Take();
416     }
417 
418 } // namespace OpenRCT2::Scripting
419 
420 #endif
421