mirror of https://github.com/OpenRCT2/OpenRCT2.git
434 lines
12 KiB
C++
434 lines
12 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2020 OpenRCT2 developers
|
|
*
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
|
*****************************************************************************/
|
|
|
|
#pragma once
|
|
|
|
#ifdef ENABLE_SCRIPTING
|
|
|
|
# include "../core/Console.hpp"
|
|
# include "../core/EnumMap.hpp"
|
|
# include "../ride/Vehicle.h"
|
|
# include "../world/Map.h"
|
|
|
|
# include <cstdio>
|
|
# include <dukglue/dukglue.h>
|
|
# include <duktape.h>
|
|
# include <optional>
|
|
# include <stdexcept>
|
|
|
|
namespace OpenRCT2::Scripting
|
|
{
|
|
template<typename T> DukValue GetObjectAsDukValue(duk_context* ctx, const std::shared_ptr<T>& value)
|
|
{
|
|
dukglue::types::DukType<std::shared_ptr<T>>::template push<T>(ctx, value);
|
|
return DukValue::take_from_stack(ctx);
|
|
}
|
|
|
|
template<typename T> T AsOrDefault(const DukValue& value, const T& defaultValue = {}) = delete;
|
|
|
|
inline std::string AsOrDefault(const DukValue& value, std::string_view defaultValue)
|
|
{
|
|
return value.type() == DukValue::STRING ? value.as_string() : std::string(defaultValue);
|
|
}
|
|
|
|
template<> inline std::string AsOrDefault(const DukValue& value, const std::string& defaultValue)
|
|
{
|
|
return value.type() == DukValue::STRING ? value.as_string() : defaultValue;
|
|
}
|
|
|
|
template<> inline int32_t AsOrDefault(const DukValue& value, const int32_t& defaultValue)
|
|
{
|
|
return value.type() == DukValue::NUMBER ? value.as_int() : defaultValue;
|
|
}
|
|
|
|
template<> inline bool AsOrDefault(const DukValue& value, const bool& defaultValue)
|
|
{
|
|
return value.type() == DukValue::BOOLEAN ? value.as_bool() : defaultValue;
|
|
}
|
|
|
|
enum class DukUndefined
|
|
{
|
|
};
|
|
constexpr DukUndefined undefined{};
|
|
|
|
/**
|
|
* Allows creation of an object on the duktape stack and setting properties on it before
|
|
* retrieving the DukValue instance of it.
|
|
*/
|
|
class DukObject
|
|
{
|
|
private:
|
|
duk_context* _ctx{};
|
|
duk_idx_t _idx = DUK_INVALID_INDEX;
|
|
|
|
public:
|
|
DukObject(duk_context* ctx)
|
|
: _ctx(ctx)
|
|
{
|
|
}
|
|
|
|
DukObject(const DukObject&) = delete;
|
|
|
|
DukObject(DukObject&& m) noexcept
|
|
{
|
|
_ctx = m._ctx;
|
|
_idx = m._idx;
|
|
m._ctx = {};
|
|
m._idx = {};
|
|
}
|
|
|
|
~DukObject()
|
|
{
|
|
PopObjectIfExists();
|
|
}
|
|
|
|
void Set(const char* name, std::nullptr_t)
|
|
{
|
|
EnsureObjectPushed();
|
|
duk_push_null(_ctx);
|
|
duk_put_prop_string(_ctx, _idx, name);
|
|
}
|
|
|
|
void Set(const char* name, DukUndefined)
|
|
{
|
|
EnsureObjectPushed();
|
|
duk_push_undefined(_ctx);
|
|
duk_put_prop_string(_ctx, _idx, name);
|
|
}
|
|
|
|
void Set(const char* name, bool value)
|
|
{
|
|
EnsureObjectPushed();
|
|
duk_push_boolean(_ctx, value);
|
|
duk_put_prop_string(_ctx, _idx, name);
|
|
}
|
|
|
|
void Set(const char* name, int32_t value)
|
|
{
|
|
EnsureObjectPushed();
|
|
duk_push_int(_ctx, value);
|
|
duk_put_prop_string(_ctx, _idx, name);
|
|
}
|
|
|
|
void Set(const char* name, uint32_t value)
|
|
{
|
|
EnsureObjectPushed();
|
|
duk_push_uint(_ctx, value);
|
|
duk_put_prop_string(_ctx, _idx, name);
|
|
}
|
|
|
|
void Set(const char* name, int64_t value)
|
|
{
|
|
EnsureObjectPushed();
|
|
duk_push_number(_ctx, value);
|
|
duk_put_prop_string(_ctx, _idx, name);
|
|
}
|
|
|
|
void Set(const char* name, uint64_t value)
|
|
{
|
|
EnsureObjectPushed();
|
|
duk_push_number(_ctx, value);
|
|
duk_put_prop_string(_ctx, _idx, name);
|
|
}
|
|
|
|
void Set(const char* name, std::string_view value)
|
|
{
|
|
EnsureObjectPushed();
|
|
duk_push_lstring(_ctx, value.data(), value.size());
|
|
duk_put_prop_string(_ctx, _idx, name);
|
|
}
|
|
|
|
void Set(const char* name, const char* value)
|
|
{
|
|
Set(name, std::string_view(value));
|
|
}
|
|
|
|
void Set(const char* name, const DukValue& value)
|
|
{
|
|
EnsureObjectPushed();
|
|
value.push();
|
|
duk_put_prop_string(_ctx, _idx, name);
|
|
}
|
|
|
|
template<typename T> void Set(const char* name, const std::optional<T>& value)
|
|
{
|
|
if (value)
|
|
{
|
|
EnsureObjectPushed();
|
|
duk_push_null(_ctx);
|
|
duk_put_prop_string(_ctx, _idx, name);
|
|
}
|
|
else
|
|
{
|
|
Set(name, *value);
|
|
}
|
|
}
|
|
|
|
DukValue Take()
|
|
{
|
|
EnsureObjectPushed();
|
|
auto result = DukValue::take_from_stack(_ctx, _idx);
|
|
_idx = DUK_INVALID_INDEX;
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
void PopObjectIfExists()
|
|
{
|
|
if (_idx != DUK_INVALID_INDEX)
|
|
{
|
|
duk_remove(_ctx, _idx);
|
|
_idx = DUK_INVALID_INDEX;
|
|
}
|
|
}
|
|
|
|
void EnsureObjectPushed()
|
|
{
|
|
if (_idx == DUK_INVALID_INDEX)
|
|
{
|
|
_idx = duk_push_object(_ctx);
|
|
}
|
|
}
|
|
};
|
|
|
|
class DukStackFrame
|
|
{
|
|
private:
|
|
duk_context* _ctx{};
|
|
duk_idx_t _top;
|
|
|
|
public:
|
|
DukStackFrame(duk_context* ctx)
|
|
: _ctx(ctx)
|
|
{
|
|
_top = duk_get_top(ctx);
|
|
}
|
|
|
|
~DukStackFrame()
|
|
{
|
|
auto top = duk_get_top(_ctx);
|
|
if (top != _top)
|
|
{
|
|
duk_set_top(_ctx, _top);
|
|
_ctx = {};
|
|
Console::Error::WriteLine("duktape stack was not returned to original state!");
|
|
}
|
|
_ctx = {};
|
|
}
|
|
|
|
DukStackFrame(const DukStackFrame&) = delete;
|
|
DukStackFrame(DukStackFrame&&) = delete;
|
|
};
|
|
|
|
/**
|
|
* Bi-directional map for converting between strings and enums / numbers.
|
|
*/
|
|
template<typename T> using DukEnumMap = EnumMap<T>;
|
|
|
|
inline duk_ret_t duk_json_decode_wrapper(duk_context* ctx, void*)
|
|
{
|
|
duk_json_decode(ctx, -1);
|
|
return 1;
|
|
}
|
|
|
|
inline std::optional<DukValue> DuktapeTryParseJson(duk_context* ctx, std::string_view json)
|
|
{
|
|
duk_push_lstring(ctx, json.data(), json.size());
|
|
if (duk_safe_call(ctx, duk_json_decode_wrapper, nullptr, 1, 1) == DUK_EXEC_SUCCESS)
|
|
{
|
|
return DukValue::take_from_stack(ctx);
|
|
}
|
|
|
|
// Pop error off stack
|
|
duk_pop(ctx);
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::string ProcessString(const DukValue& value);
|
|
|
|
template<typename T> DukValue ToDuk(duk_context* ctx, const T& value) = delete;
|
|
template<typename T> T FromDuk(const DukValue& s) = delete;
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const std::nullptr_t&)
|
|
{
|
|
duk_push_null(ctx);
|
|
return DukValue::take_from_stack(ctx);
|
|
}
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const DukUndefined&)
|
|
{
|
|
duk_push_undefined(ctx);
|
|
return DukValue::take_from_stack(ctx);
|
|
}
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const bool& value)
|
|
{
|
|
duk_push_boolean(ctx, value);
|
|
return DukValue::take_from_stack(ctx);
|
|
}
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const uint8_t& value)
|
|
{
|
|
duk_push_int(ctx, value);
|
|
return DukValue::take_from_stack(ctx);
|
|
}
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const int32_t& value)
|
|
{
|
|
duk_push_int(ctx, value);
|
|
return DukValue::take_from_stack(ctx);
|
|
}
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const int64_t& value)
|
|
{
|
|
duk_push_number(ctx, value);
|
|
return DukValue::take_from_stack(ctx);
|
|
}
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const std::string_view& value)
|
|
{
|
|
duk_push_lstring(ctx, value.data(), value.size());
|
|
return DukValue::take_from_stack(ctx);
|
|
}
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const std::string& value)
|
|
{
|
|
return ToDuk(ctx, std::string_view(value));
|
|
}
|
|
|
|
template<size_t TLen> inline DukValue ToDuk(duk_context* ctx, const char (&value)[TLen])
|
|
{
|
|
duk_push_string(ctx, value);
|
|
return DukValue::take_from_stack(ctx);
|
|
}
|
|
|
|
template<typename T> inline DukValue ToDuk(duk_context* ctx, const std::optional<T>& value)
|
|
{
|
|
return value ? ToDuk(ctx, *value) : ToDuk(ctx, nullptr);
|
|
}
|
|
|
|
template<> CoordsXY inline FromDuk(const DukValue& d)
|
|
{
|
|
CoordsXY result;
|
|
result.x = AsOrDefault(d["x"], 0);
|
|
result.y = AsOrDefault(d["y"], 0);
|
|
return result;
|
|
}
|
|
|
|
template<> DukValue inline ToDuk(duk_context* ctx, const CoordsXY& coords)
|
|
{
|
|
DukObject dukCoords(ctx);
|
|
dukCoords.Set("x", coords.x);
|
|
dukCoords.Set("y", coords.y);
|
|
return dukCoords.Take();
|
|
}
|
|
|
|
template<> DukValue inline ToDuk(duk_context* ctx, const ScreenCoordsXY& coords)
|
|
{
|
|
DukObject dukCoords(ctx);
|
|
dukCoords.Set("x", coords.x);
|
|
dukCoords.Set("y", coords.y);
|
|
return dukCoords.Take();
|
|
}
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const CoordsXYZ& value)
|
|
{
|
|
if (value.IsNull())
|
|
{
|
|
return ToDuk(ctx, nullptr);
|
|
}
|
|
|
|
DukObject dukCoords(ctx);
|
|
dukCoords.Set("x", value.x);
|
|
dukCoords.Set("y", value.y);
|
|
dukCoords.Set("z", value.z);
|
|
return dukCoords.Take();
|
|
}
|
|
|
|
template<> inline CoordsXYZ FromDuk(const DukValue& value)
|
|
{
|
|
CoordsXYZ result;
|
|
if (value.type() == DukValue::Type::OBJECT)
|
|
{
|
|
result.x = AsOrDefault(value["x"], 0);
|
|
result.y = AsOrDefault(value["y"], 0);
|
|
result.z = AsOrDefault(value["z"], 0);
|
|
}
|
|
else
|
|
{
|
|
result.SetNull();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const CoordsXYZD& value)
|
|
{
|
|
if (value.IsNull())
|
|
{
|
|
return ToDuk(ctx, nullptr);
|
|
}
|
|
|
|
DukObject dukCoords(ctx);
|
|
dukCoords.Set("x", value.x);
|
|
dukCoords.Set("y", value.y);
|
|
dukCoords.Set("z", value.z);
|
|
dukCoords.Set("direction", value.direction);
|
|
return dukCoords.Take();
|
|
}
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const GForces& value)
|
|
{
|
|
DukObject dukGForces(ctx);
|
|
dukGForces.Set("lateralG", value.LateralG);
|
|
dukGForces.Set("verticalG", value.VerticalG);
|
|
return dukGForces.Take();
|
|
}
|
|
|
|
template<> inline CoordsXYZD FromDuk(const DukValue& value)
|
|
{
|
|
CoordsXYZD result;
|
|
if (value.type() == DukValue::Type::OBJECT)
|
|
{
|
|
result.x = AsOrDefault(value["x"], 0);
|
|
result.y = AsOrDefault(value["y"], 0);
|
|
result.z = AsOrDefault(value["z"], 0);
|
|
result.direction = AsOrDefault(value["direction"], 0);
|
|
}
|
|
else
|
|
{
|
|
result.SetNull();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
template<> inline DukValue ToDuk(duk_context* ctx, const ScreenSize& value)
|
|
{
|
|
DukObject dukCoords(ctx);
|
|
dukCoords.Set("width", value.width);
|
|
dukCoords.Set("height", value.height);
|
|
return dukCoords.Take();
|
|
}
|
|
|
|
template<> ObjectEntryIndex inline FromDuk(const DukValue& d)
|
|
{
|
|
if (d.type() == DukValue::Type::NUMBER)
|
|
{
|
|
auto value = d.as_int();
|
|
if (value >= 0 && value <= std::numeric_limits<ObjectEntryIndex>::max())
|
|
{
|
|
return static_cast<ObjectEntryIndex>(value);
|
|
}
|
|
}
|
|
return OBJECT_ENTRY_INDEX_NULL;
|
|
}
|
|
|
|
} // namespace OpenRCT2::Scripting
|
|
|
|
#endif
|