Swap Duktape for quickjs-ng

Co-authored-by: Basssiiie <Basssiiie@users.noreply.github.com>
Co-authored-by: Tulio Leao <tupaschoal@gmail.com>
This commit is contained in:
Michael Bernardi
2025-09-29 02:05:00 +10:00
parent 69518538fd
commit 16cd1c07ff
143 changed files with 97500 additions and 120982 deletions
+1
View File
@@ -19,3 +19,4 @@ jobs:
with:
build_dir: 'build'
cmake_args: '-G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Debug -DDISABLE_DISCORD_RPC=ON'
file_filter: 'cpp,c,cc,cxx'
+4 -4
View File
@@ -48,12 +48,13 @@ if (APPLE)
set(CMAKE_SYSTEM_PROCESSOR "${CMAKE_OSX_ARCHITECTURES}" CACHE STRING "")
endif ()
project(openrct2 CXX)
# C Language required for quickjs-ng
project(openrct2 CXX C)
include(cmake/platform.cmake)
include(CMakeDependentOption)
# vcpkg includes its own copy of duktapeConfig.cmake; only include ours as needed.
# vcpkg includes its own copy of *.cmake; only include ours as needed.
if (NOT MSVC)
include(FindPkgConfig)
set(CMAKE_PREFIX_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}")
@@ -267,7 +268,7 @@ endfunction ()
# Add check flags to a compile TARGET
function (SET_CHECK_CXX_FLAGS _TARGET)
target_compile_options("${_TARGET}" PRIVATE "${SUPPORTED_CHECK_CXX_COMPILER_FLAGS}")
target_compile_options("${_TARGET}" PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${SUPPORTED_CHECK_CXX_COMPILER_FLAGS}>)
endfunction()
# Check if a flag exists and add it to the compiler and the linker options
@@ -395,7 +396,6 @@ endif()
# graphics files (g2.dat, fonts.dat, palettes.dat, tracks.dat)
if (NOT CMAKE_CROSSCOMPILING)
set(graphics_files "g2" "fonts" "palettes" "tracks")
foreach(graphics_file ${graphics_files})
set(output_file "${graphics_file}.dat")
set(json_file "${ROOT_DIR}/resources/${graphics_file}/sprites.json")
+3 -2
View File
@@ -69,7 +69,8 @@
<RuntimeLibrary Condition="'$(UseSharedLibs)'=='true'">MultiThreadedDLL</RuntimeLibrary>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<TreatWarningAsError>true</TreatWarningAsError>
<AdditionalOptions>/utf-8 /std:c++20 /permissive- /Zc:externConstexpr /Zc:char8_t-</AdditionalOptions>
<AdditionalOptions>/utf-8 /permissive- /Zc:externConstexpr /Zc:char8_t-</AdditionalOptions>
<LanguageStandard>stdcpp20</LanguageStandard>
</ClCompile>
<Link>
<LargeAddressAware Condition="'$(Platform)'=='Win32'">true</LargeAddressAware>
@@ -119,7 +120,7 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup>
<IncludePath>$(SolutionDir)src;$(SolutionDir)src\thirdparty;$(SolutionDir)src\thirdparty\duktape;$(SolutionDir)lib\$(Platform)\include;$(SolutionDir)lib\$(Platform)\include\SDL2;$(SolutionDir)lib\$(Platform)\include\crashpad;$(IncludePath)</IncludePath>
<IncludePath>$(SolutionDir)src;$(SolutionDir)src\thirdparty;$(SolutionDir)src\thirdparty\quickjs-ng;$(SolutionDir)lib\$(Platform)\include;$(SolutionDir)lib\$(Platform)\include\SDL2;$(SolutionDir)lib\$(Platform)\include\crashpad;$(IncludePath)</IncludePath>
<LibraryPath Condition="'$(Configuration)'=='Debug'">$(SolutionDir)lib\$(Platform)\debug\lib;$(LibraryPath)</LibraryPath>
<LibraryPath Condition="'$(Configuration)'!='Debug'">$(SolutionDir)lib\$(Platform)\lib;$(LibraryPath)</LibraryPath>
<LinkIncremental />
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.10)
project(openrct2-android CXX)
project(openrct2-android CXX C)
set(CMAKE_VERBOSE_MAKEFILE on)
@@ -226,7 +226,7 @@ if (CCache_FOUND)
endif (CCache_FOUND)
file(GLOB_RECURSE LIBOPENRCT2_SOURCES
"${ORCT2_ROOT}/src/thirdparty/duktape/duktape.cpp"
"${ORCT2_ROOT}/src/thirdparty/quickjs-ng/quickjs-amalgam.c"
"${ORCT2_ROOT}/src/openrct2/*.cpp"
"${ORCT2_ROOT}/src/openrct2/*.h"
"${ORCT2_ROOT}/src/openrct2/*.hpp")
@@ -255,13 +255,13 @@ target_link_libraries(openrct2-ui openrct2 android stdc++ GLESv1_CM GLESv2 SDL2m
add_executable(openrct2-cli ${OPENRCT2_CLI_SOURCES})
target_link_libraries(openrct2-cli openrct2 android stdc++ GLESv1_CM GLESv2)
target_include_directories(openrct2 PRIVATE "${ORCT2_ROOT}/src/thirdparty/duktape/")
target_include_directories(openrct2 SYSTEM PRIVATE "${ORCT2_ROOT}/src/thirdparty/quickjs-ng")
target_include_directories(openrct2 SYSTEM PRIVATE "${ORCT2_ROOT}/src/thirdparty")
target_include_directories(openrct2-ui PRIVATE "${ORCT2_ROOT}/src/thirdparty/duktape/")
target_include_directories(openrct2-ui SYSTEM PRIVATE "${ORCT2_ROOT}/src/thirdparty/quickjs-ng")
target_include_directories(openrct2-ui PRIVATE "${ORCT2_ROOT}/src")
target_include_directories(openrct2-ui SYSTEM PRIVATE "${ORCT2_ROOT}/src/thirdparty")
target_include_directories(openrct2-cli PRIVATE "${ORCT2_ROOT}/src")
target_include_directories(openrct2-cli SYSTEM PRIVATE "${ORCT2_ROOT}/src/thirdparty")
target_include_directories(openrct2 PRIVATE "/opt/openrct2/include/nlohmann/../")
target_include_directories(openrct2-ui PRIVATE "/opt/openrct2/include/nlohmann/../")
target_include_directories(openrct2-ui PRIVATE "/opt/openrct2/include/nlohmann/../")
+1 -1
View File
@@ -156,7 +156,7 @@ if (ENABLE_HEADERS_CHECK AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_library(openrct2-headers-check OBJECT ${OPENRCT2_HEADERS_CHECK})
set_target_properties(openrct2-headers-check PROPERTIES LINKER_LANGUAGE CXX)
set_source_files_properties(${OPENRCT2_HEADERS_CHECK} PROPERTIES LANGUAGE CXX)
add_definitions("-x c++ -Wno-pragma-once-outside-header -Wno-unused-const-variable")
target_compile_options(openrct2-headers-check PRIVATE -Wno-pragma-once-outside-header -Wno-unused-const-variable)
get_target_property(OPENRCT2_INCLUDE_DIRS openrct2 INCLUDE_DIRECTORIES)
set_target_properties(openrct2-headers-check PROPERTIES INCLUDE_DIRECTORIES "${OPENRCT2_INCLUDE_DIRS}")
endif ()
+7 -1
View File
@@ -271,7 +271,7 @@ public:
callback(result, std::string(path).c_str());
}
},
trackDesign);
false, trackDesign);
return w;
}
case WindowClass::manageTrackDesign:
@@ -1111,6 +1111,12 @@ public:
CloseByClass(WindowClass::trackDesignPlace);
}
void Cleanup() override
{
CloseByCondition([](WindowBase* w) { return true; });
WindowCullDead();
}
/**
* Finds the first window with the specified window class.
* rct2: 0x006EA8A0
+17 -5
View File
@@ -41,12 +41,12 @@ extern void EmscriptenSaveGame(bool isTrackDesign, bool isAutosave, LoadSaveType
namespace OpenRCT2::Ui::FileBrowser
{
static LoadSaveCallback _loadSaveCallback;
static bool _loadSaveCallbackIsJs = false;
WindowBase* OpenPreferred(
LoadSaveAction action, LoadSaveType type, u8string defaultPath, LoadSaveCallback callback, TrackDesign* trackDesign)
LoadSaveAction action, LoadSaveType type, u8string defaultPath, LoadSaveCallback callback, bool isJsCallback,
TrackDesign* trackDesign)
{
RegisterCallback(callback);
#ifdef __EMSCRIPTEN__
if (action == LoadSaveAction::save)
{
@@ -69,16 +69,18 @@ namespace OpenRCT2::Ui::FileBrowser
const bool isSave = (action == LoadSaveAction::save);
const auto defaultDirectory = GetDir(type);
RegisterCallback(callback, isJsCallback);
const u8string path = OpenSystemFileBrowser(isSave, type, defaultDirectory, defaultPath, trackDesign);
if (!path.empty())
{
Select(path.c_str(), action, type, trackDesign);
}
UnregisterJSCallback();
return nullptr;
}
// Use built-in load/save window
return Windows::LoadsaveOpen(action, type, defaultPath, callback, trackDesign);
return Windows::LoadsaveOpen(action, type, defaultPath, callback, isJsCallback, trackDesign);
}
bool ListItemSort(LoadSaveListItem& a, LoadSaveListItem& b)
@@ -227,9 +229,19 @@ namespace OpenRCT2::Ui::FileBrowser
return result;
}
void RegisterCallback(LoadSaveCallback callback)
void RegisterCallback(LoadSaveCallback callback, bool isJsCallback)
{
_loadSaveCallback = callback;
_loadSaveCallbackIsJs = isJsCallback;
}
void UnregisterJSCallback()
{
// This is very hacky but for a silly confluence of reasons we have to clear the callback if it is a callback to
// javascript plugin code, but not if it is to normal C++ code.
// https://github.com/mrmbernardi/OpenRCT2/pull/11#issuecomment-3448197606
if (_loadSaveCallbackIsJs)
_loadSaveCallback = {};
}
void InvokeCallback(ModalResult result, const utf8* path)
+4 -2
View File
@@ -58,14 +58,16 @@ namespace OpenRCT2::Ui::FileBrowser
u8string GetFilterPatternByType(LoadSaveType type, bool isSave, const TrackDesign* trackDesign = nullptr);
u8string RemovePatternWildcard(u8string_view pattern);
u8string GetDir(LoadSaveType type);
void RegisterCallback(LoadSaveCallback callback);
void RegisterCallback(LoadSaveCallback callback, bool isJsCallback);
void UnregisterJSCallback();
void InvokeCallback(ModalResult result, const utf8* path);
void Select(const char* path, LoadSaveAction action, LoadSaveType type, TrackDesign* trackDesignPtr);
StringId GetTitleStringId(LoadSaveType type, bool isSave);
u8string OpenSystemFileBrowser(
bool isSave, LoadSaveType type, u8string defaultDirectory, u8string defaultPath, const TrackDesign* trackDesign);
WindowBase* OpenPreferred(
LoadSaveAction action, LoadSaveType type, u8string defaultPath, LoadSaveCallback callback, TrackDesign* trackDesign);
LoadSaveAction action, LoadSaveType type, u8string defaultPath, LoadSaveCallback callback, bool isJsCallback,
TrackDesign* trackDesign);
} // namespace OpenRCT2::Ui::FileBrowser
#ifdef __EMSCRIPTEN__
+2
View File
@@ -94,6 +94,7 @@
<ClInclude Include="scripting\ScUi.hpp" />
<ClInclude Include="scripting\ScViewport.hpp" />
<ClInclude Include="scripting\ScWidget.hpp" />
<ClInclude Include="scripting\ScWindow.h" />
<ClInclude Include="scripting\ScWindow.hpp" />
<ClInclude Include="scripting\UiExtensions.h" />
<ClInclude Include="ProvisionalElements.h" />
@@ -155,6 +156,7 @@
<ClCompile Include="scripting\CustomListView.cpp" />
<ClCompile Include="scripting\CustomMenu.cpp" />
<ClCompile Include="scripting\CustomWindow.cpp" />
<ClCompile Include="scripting\ScWindow.cpp" />
<ClCompile Include="scripting\UiExtensions.cpp" />
<ClCompile Include="ProvisionalElements.cpp" />
<ClCompile Include="SDLException.cpp" />
+84 -124
View File
@@ -18,6 +18,7 @@
#include <openrct2/drawing/ImageImporter.h>
#include <openrct2/drawing/X8DrawingEngine.h>
#include <openrct2/scripting/Plugin.h>
#include <thirdparty/base64.hpp>
using namespace OpenRCT2::Drawing;
@@ -47,7 +48,7 @@ namespace OpenRCT2::Scripting
int32_t Height;
int32_t Stride;
PixelDataPaletteKind Palette;
DukValue Data;
JSValue Data;
};
struct AllocatedImageList
@@ -141,37 +142,37 @@ namespace OpenRCT2::Scripting
scriptEngine.SubscribeToPluginStoppedEvent([](std::shared_ptr<Plugin> plugin) -> void { FreeCustomImages(plugin); });
}
DukValue DukGetImageInfo(duk_context* ctx, ImageIndex id)
JSValue JSGetImageInfo(JSContext* ctx, ImageIndex id)
{
auto* g1 = GfxGetG1Element(id);
if (g1 == nullptr)
{
return ToDuk(ctx, undefined);
return JS_UNDEFINED;
}
DukObject obj(ctx);
obj.Set("id", id);
obj.Set("offset", ToDuk<ScreenCoordsXY>(ctx, { g1->xOffset, g1->yOffset }));
obj.Set("width", g1->width);
obj.Set("height", g1->height);
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "id", JS_NewInt32(ctx, id));
JS_SetPropertyStr(ctx, obj, "offset", ToJSValue(ctx, ScreenCoordsXY{ g1->xOffset, g1->yOffset }));
JS_SetPropertyStr(ctx, obj, "width", JS_NewInt32(ctx, g1->width));
JS_SetPropertyStr(ctx, obj, "height", JS_NewInt32(ctx, g1->height));
obj.Set("hasTransparent", g1->flags.has(G1Flag::hasTransparency));
obj.Set("isRLE", g1->flags.has(G1Flag::hasRLECompression));
obj.Set("isPalette", g1->flags.has(G1Flag::isPalette));
obj.Set("noZoom", g1->flags.has(G1Flag::noZoomDraw));
JS_SetPropertyStr(ctx, obj, "hasTransparent", JS_NewBool(ctx, g1->flags.has(G1Flag::hasTransparency)));
JS_SetPropertyStr(ctx, obj, "isRLE", JS_NewBool(ctx, g1->flags.has(G1Flag::hasRLECompression)));
JS_SetPropertyStr(ctx, obj, "isPalette", JS_NewBool(ctx, g1->flags.has(G1Flag::isPalette)));
JS_SetPropertyStr(ctx, obj, "noZoom", JS_NewBool(ctx, g1->flags.has(G1Flag::noZoomDraw)));
if (g1->flags.has(G1Flag::hasZoomSprite))
{
obj.Set("nextZoomId", id - g1->zoomedOffset);
JS_SetPropertyStr(ctx, obj, "nextZoomId", JS_NewInt32(ctx, id - g1->zoomedOffset));
}
else
{
obj.Set("nextZoomId", undefined);
JS_SetPropertyStr(ctx, obj, "nextZoomId", JS_UNDEFINED);
}
return obj.Take();
return obj;
}
static const char* GetPixelDataTypeForG1(const G1Element& g1)
static std::string_view GetPixelDataTypeForG1(const G1Element& g1)
{
if (g1.flags.has(G1Flag::hasRLECompression))
return "rle";
@@ -180,83 +181,55 @@ namespace OpenRCT2::Scripting
return "raw";
}
DukValue DukGetImagePixelData(duk_context* ctx, ImageIndex id)
JSValue JSGetImagePixelData(JSContext* ctx, ImageIndex id)
{
auto* g1 = GfxGetG1Element(id);
if (g1 == nullptr)
{
return ToDuk(ctx, undefined);
return JS_UNDEFINED;
}
auto dataSize = G1CalculateDataSize(g1);
auto* type = GetPixelDataTypeForG1(*g1);
auto type = GetPixelDataTypeForG1(*g1);
// Copy the G1 data to a JS buffer wrapped in a Uint8Array
duk_push_fixed_buffer(ctx, dataSize);
duk_size_t bufferSize{};
auto* buffer = duk_get_buffer_data(ctx, -1, &bufferSize);
if (buffer != nullptr && bufferSize == dataSize)
{
std::memcpy(buffer, g1->offset, dataSize);
}
duk_push_buffer_object(ctx, -1, 0, dataSize, DUK_BUFOBJ_UINT8ARRAY);
duk_remove(ctx, -2);
auto data = DukValue::take_from_stack(ctx, -1);
JSValue data = JS_NewUint8ArrayCopy(ctx, g1->offset, dataSize);
DukObject obj(ctx);
obj.Set("type", type);
obj.Set("width", g1->width);
obj.Set("height", g1->height);
obj.Set("data", data);
return obj.Take();
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "type", JSFromStdString(ctx, type));
JS_SetPropertyStr(ctx, obj, "width", JS_NewInt32(ctx, g1->width));
JS_SetPropertyStr(ctx, obj, "height", JS_NewInt32(ctx, g1->height));
JS_SetPropertyStr(ctx, obj, "data", data);
return obj;
}
static std::vector<uint8_t> GetBufferFromDukStack(duk_context* ctx)
static std::vector<uint8_t> GetDataFromBufferLikeObject(JSContext* ctx, JSValue data)
{
std::vector<uint8_t> result;
duk_size_t bufferLen{};
const auto* buffer = reinterpret_cast<uint8_t*>(duk_get_buffer_data(ctx, -1, &bufferLen));
if (buffer != nullptr)
{
result.resize(bufferLen);
std::memcpy(result.data(), buffer, bufferLen);
}
return result;
}
static std::vector<uint8_t> DukGetDataFromBufferLikeObject(const DukValue& data)
{
std::vector<uint8_t> result;
auto ctx = data.context();
if (data.is_array())
if (JS_IsArray(data))
{
// From array of numbers
data.push();
auto len = duk_get_length(ctx, -1);
result.resize(len);
for (duk_uarridx_t i = 0; i < len; i++)
int64_t arrSz = 0;
JS_GetLength(ctx, data, &arrSz);
if (arrSz > 0)
{
if (duk_get_prop_index(ctx, -1, i))
{
result[i] = duk_get_int(ctx, -1) & 0xFF;
duk_pop(ctx);
}
result.resize(arrSz);
JSIterateArray(ctx, data, [&result](JSContext* ctx2, JSValue val) { result.push_back(JSToInt(ctx2, val)); });
}
duk_pop(ctx);
}
else if (data.type() == DukValue::Type::STRING)
else if (JS_IsString(data))
{
// From base64 string
data.push();
duk_base64_decode(ctx, -1);
result = GetBufferFromDukStack(ctx);
duk_pop(ctx);
std::string str = JSToStdString(ctx, data);
result = base64::decode_into<std::vector<uint8_t>>(str);
}
else if (data.type() == DukValue::Type::OBJECT)
else if (JS_IsArrayBuffer(data))
{
// From Uint8Array
data.push();
result = GetBufferFromDukStack(ctx);
duk_pop(ctx);
size_t sz = 0;
uint8_t* arr = JS_GetUint8Array(ctx, &sz, data);
if (arr)
{
result = std::vector<uint8_t>(arr, arr + sz);
}
}
return result;
}
@@ -290,14 +263,14 @@ namespace OpenRCT2::Scripting
}
}
static std::vector<uint8_t> GetBufferFromPixelData(duk_context* ctx, PixelData& pixelData)
static std::vector<uint8_t> GetBufferFromPixelData(JSContext* ctx, PixelData& pixelData)
{
std::vector<uint8_t> imageData;
switch (pixelData.Type)
{
case PixelDataKind::Raw:
{
auto data = DukGetDataFromBufferLikeObject(pixelData.Data);
auto data = GetDataFromBufferLikeObject(ctx, pixelData.Data);
if (pixelData.Stride != pixelData.Width)
{
// Make sure data is expected size for RemovePadding
@@ -312,7 +285,7 @@ namespace OpenRCT2::Scripting
}
case PixelDataKind::Rle:
{
imageData = DukGetDataFromBufferLikeObject(pixelData.Data);
imageData = GetDataFromBufferLikeObject(ctx, pixelData.Data);
break;
}
case PixelDataKind::Png:
@@ -320,7 +293,7 @@ namespace OpenRCT2::Scripting
auto imageFormat = pixelData.Palette == PixelDataPaletteKind::Keep ? ImageFormat::png : ImageFormat::png32;
auto palette = pixelData.Palette == PixelDataPaletteKind::Keep ? Palette::KeepIndices : Palette::OpenRCT2;
auto importMode = getImportModeFromPalette(pixelData.Palette);
auto pngData = DukGetDataFromBufferLikeObject(pixelData.Data);
auto pngData = GetDataFromBufferLikeObject(ctx, pixelData.Data);
auto image = Imaging::ReadFromBuffer(pngData, imageFormat);
constexpr ImportFlags flags = { ImportFlag::rle };
ImageImportMeta meta = { { 0, 0 }, palette, flags, importMode };
@@ -341,49 +314,40 @@ namespace OpenRCT2::Scripting
return imageData;
}
template<>
PixelDataKind FromDuk(const DukValue& d)
static PixelDataKind PixelDataKindFromJS(const std::string& s)
{
if (d.type() == DukValue::Type::STRING)
{
auto& s = d.as_string();
if (s == "raw")
return PixelDataKind::Raw;
if (s == "rle")
return PixelDataKind::Rle;
if (s == "palette")
return PixelDataKind::Palette;
if (s == "png")
return PixelDataKind::Png;
}
if (s == "raw")
return PixelDataKind::Raw;
if (s == "rle")
return PixelDataKind::Rle;
if (s == "palette")
return PixelDataKind::Palette;
if (s == "png")
return PixelDataKind::Png;
return PixelDataKind::Unknown;
}
template<>
PixelDataPaletteKind FromDuk(const DukValue& d)
static PixelDataPaletteKind PixelDataPaletteKindFromJS(const std::string& s)
{
if (d.type() == DukValue::Type::STRING)
{
auto& s = d.as_string();
if (s == "keep")
return PixelDataPaletteKind::Keep;
if (s == "closest")
return PixelDataPaletteKind::Closest;
if (s == "dither")
return PixelDataPaletteKind::Dither;
}
if (s == "keep")
return PixelDataPaletteKind::Keep;
if (s == "closest")
return PixelDataPaletteKind::Closest;
if (s == "dither")
return PixelDataPaletteKind::Dither;
return PixelDataPaletteKind::None;
}
static PixelData GetPixelDataFromDuk(const DukValue& dukPixelData)
static PixelData GetPixelDataFromJS(JSContext* ctx, JSValue jsPixelData)
{
PixelData pixelData;
pixelData.Type = FromDuk<PixelDataKind>(dukPixelData["type"]);
pixelData.Palette = FromDuk<PixelDataPaletteKind>(dukPixelData["palette"]);
pixelData.Width = AsOrDefault(dukPixelData["width"], 0);
pixelData.Height = AsOrDefault(dukPixelData["height"], 0);
pixelData.Stride = AsOrDefault(dukPixelData["stride"], pixelData.Width);
pixelData.Data = dukPixelData["data"];
pixelData.Type = PixelDataKindFromJS(JSToStdString(ctx, jsPixelData, "type"));
pixelData.Palette = PixelDataPaletteKindFromJS(JSToStdString(ctx, jsPixelData, "palette"));
pixelData.Width = AsOrDefault(ctx, jsPixelData, "width", static_cast<int32_t>(0));
pixelData.Height = AsOrDefault(ctx, jsPixelData, "height", static_cast<int32_t>(0));
pixelData.Stride = AsOrDefault(ctx, jsPixelData, "stride", static_cast<int32_t>(pixelData.Width));
// Note: this must be JS_FreeValued
pixelData.Data = JS_GetPropertyStr(ctx, jsPixelData, "data");
return pixelData;
}
@@ -414,21 +378,15 @@ namespace OpenRCT2::Scripting
DrawingEngineInvalidateImage(id);
}
void DukSetPixelData(duk_context* ctx, ImageIndex id, const DukValue& dukPixelData)
void JSSetPixelData(JSContext* ctx, ImageIndex id, JSValue jsPixelData)
{
auto pixelData = GetPixelDataFromDuk(dukPixelData);
try
{
auto newData = GetBufferFromPixelData(ctx, pixelData);
ReplacePixelDataForImage(id, pixelData, std::move(newData));
}
catch (const std::runtime_error& e)
{
duk_error(ctx, DUK_ERR_ERROR, e.what());
}
auto pixelData = GetPixelDataFromJS(ctx, jsPixelData);
auto newData = GetBufferFromPixelData(ctx, pixelData);
ReplacePixelDataForImage(id, pixelData, std::move(newData));
JS_FreeValue(ctx, pixelData.Data);
}
void DukDrawCustomImage(ScriptEngine& scriptEngine, ImageIndex id, ScreenSize size, const DukValue& callback)
void JSDrawCustomImage(ScriptEngine& scriptEngine, ImageIndex id, ScreenSize size, const JSCallback& callback)
{
auto* ctx = scriptEngine.GetContext();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
@@ -464,10 +422,12 @@ namespace OpenRCT2::Scripting
rt.bits = reinterpret_cast<PaletteIndex*>(g1->offset);
}
auto dukG = GetObjectAsDukValue(ctx, std::make_shared<ScGraphicsContext>(ctx, rt));
drawingEngine->BeginDraw();
scriptEngine.ExecutePluginCall(plugin, callback, { dukG }, false);
drawingEngine->EndDraw();
if (callback.IsValid())
{
drawingEngine->BeginDraw();
scriptEngine.ExecutePluginCall(plugin, callback.callback, { gScGraphicsContext.New(ctx, rt) }, false);
drawingEngine->EndDraw();
}
if (createNewImage)
{
+4 -6
View File
@@ -13,8 +13,6 @@
#include <memory>
#include <openrct2/drawing/Image.h>
#include <openrct2/drawing/ImageId.hpp>
#include <openrct2/scripting/Duktape.hpp>
#include <openrct2/scripting/Plugin.h>
#include <openrct2/scripting/ScriptEngine.h>
@@ -24,10 +22,10 @@ namespace OpenRCT2::Scripting
std::optional<ImageList> AllocateCustomImages(const std::shared_ptr<Plugin>& plugin, uint32_t count);
bool FreeCustomImages(const std::shared_ptr<Plugin>& plugin, ImageList range);
bool DoesPluginOwnImage(const std::shared_ptr<Plugin>& plugin, ImageIndex index);
DukValue DukGetImageInfo(duk_context* ctx, ImageIndex id);
DukValue DukGetImagePixelData(duk_context* ctx, ImageIndex id);
void DukSetPixelData(duk_context* ctx, ImageIndex id, const DukValue& dukPixelData);
void DukDrawCustomImage(ScriptEngine& scriptEngine, ImageIndex id, ScreenSize size, const DukValue& callback);
JSValue JSGetImageInfo(JSContext* ctx, ImageIndex id);
JSValue JSGetImagePixelData(JSContext* ctx, ImageIndex id);
void JSSetPixelData(JSContext* ctx, ImageIndex id, JSValue jsPixelData);
void JSDrawCustomImage(ScriptEngine& scriptEngine, ImageIndex id, ScreenSize size, const JSCallback& callback);
} // namespace OpenRCT2::Scripting
+77 -106
View File
@@ -34,12 +34,11 @@ namespace OpenRCT2::Scripting
{
constexpr size_t kColumnHeaderHeight = kListRowHeight + 1;
template<>
ColumnSortOrder FromDuk(const DukValue& d)
ColumnSortOrder ColumnSortOrderFromJS(JSContext* ctx, JSValue d)
{
if (d.type() == DukValue::Type::STRING)
if (JS_IsString(d))
{
auto s = d.as_string();
auto s = JSToStdString(ctx, d);
if (s == "ascending")
return ColumnSortOrder::Ascending;
if (s == "descending")
@@ -48,44 +47,36 @@ namespace OpenRCT2::Scripting
return ColumnSortOrder::None;
}
template<>
DukValue ToDuk(duk_context* ctx, const ColumnSortOrder& value)
static JSValue ColumnSortOrderToJS(JSContext* ctx, ColumnSortOrder value)
{
switch (value)
{
case ColumnSortOrder::Ascending:
return ToDuk(ctx, "ascending");
return JSFromStdString(ctx, "ascending");
case ColumnSortOrder::Descending:
return ToDuk(ctx, "descending");
return JSFromStdString(ctx, "descending");
default:
return ToDuk(ctx, "none");
return JSFromStdString(ctx, "none");
}
}
template<>
std::optional<int32_t> FromDuk(const DukValue& d)
{
if (d.type() == DukValue::Type::NUMBER)
{
return d.as_int();
}
return std::nullopt;
}
template<>
ListViewColumn FromDuk(const DukValue& d)
ListViewColumn ListViewColumnFromJS(JSContext* ctx, JSValue d)
{
ListViewColumn result;
result.CanSort = AsOrDefault(d["canSort"], false);
result.SortOrder = FromDuk<ColumnSortOrder>(d["sortOrder"]);
result.Header = AsOrDefault(d["header"], "");
result.HeaderTooltip = AsOrDefault(d["headerTooltip"], "");
result.MinWidth = FromDuk<std::optional<int32_t>>(d["minWidth"]);
result.MaxWidth = FromDuk<std::optional<int32_t>>(d["maxWidth"]);
result.RatioWidth = FromDuk<std::optional<int32_t>>(d["ratioWidth"]);
if (d["width"].type() == DukValue::Type::NUMBER)
result.CanSort = AsOrDefault(ctx, d, "canSort", false);
JSValue sortOrderVal = JS_GetPropertyStr(ctx, d, "sortOrder");
result.SortOrder = ColumnSortOrderFromJS(ctx, sortOrderVal);
JS_FreeValue(ctx, sortOrderVal);
result.Header = AsOrDefault(ctx, d, "header", "");
result.HeaderTooltip = AsOrDefault(ctx, d, "headerTooltip", "");
result.MinWidth = JSToOptionalInt(ctx, d, "minWidth");
result.MaxWidth = JSToOptionalInt(ctx, d, "maxWidth");
result.RatioWidth = JSToOptionalInt(ctx, d, "ratioWidth");
auto width = JSToOptionalInt(ctx, d, "width");
if (width.has_value())
{
result.MinWidth = d["width"].as_int();
result.MinWidth = width.value();
result.MaxWidth = result.MinWidth;
result.RatioWidth = std::nullopt;
}
@@ -96,45 +87,43 @@ namespace OpenRCT2::Scripting
return result;
}
template<>
DukValue ToDuk(duk_context* ctx, const ListViewColumn& value)
JSValue ListViewColumnToJS(JSContext* ctx, const ListViewColumn& value)
{
DukObject obj(ctx);
obj.Set("canSort", value.CanSort);
obj.Set("sortOrder", ToDuk(ctx, value.SortOrder));
obj.Set("header", value.Header);
obj.Set("headerTooltip", value.HeaderTooltip);
obj.Set("minWidth", value.MinWidth);
obj.Set("maxWidth", value.MaxWidth);
obj.Set("ratioWidth", value.RatioWidth);
obj.Set("width", value.Width);
return obj.Take();
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "canSort", JS_NewBool(ctx, value.CanSort));
JS_SetPropertyStr(ctx, obj, "sortOrder", ColumnSortOrderToJS(ctx, value.SortOrder));
JS_SetPropertyStr(ctx, obj, "header", JSFromStdString(ctx, value.Header));
JS_SetPropertyStr(ctx, obj, "headerTooltip", JSFromStdString(ctx, value.HeaderTooltip));
JS_SetPropertyStr(
ctx, obj, "minWidth", value.MinWidth.has_value() ? JS_NewInt32(ctx, value.MinWidth.value()) : JS_NULL);
JS_SetPropertyStr(
ctx, obj, "maxWidth", value.MaxWidth.has_value() ? JS_NewInt32(ctx, value.MaxWidth.value()) : JS_NULL);
JS_SetPropertyStr(
ctx, obj, "ratioWidth", value.RatioWidth.has_value() ? JS_NewInt32(ctx, value.RatioWidth.value()) : JS_NULL);
JS_SetPropertyStr(ctx, obj, "width", JS_NewInt32(ctx, value.Width));
return obj;
}
template<>
ListViewItem FromDuk(const DukValue& d)
ListViewItem ListViewItemFromJS(JSContext* ctx, JSValue d)
{
ListViewItem result;
if (d.type() == DukValue::Type::STRING)
if (JS_IsString(d))
{
result = ListViewItem(ProcessString(d));
result = ListViewItem(JSToStdString(ctx, d));
}
else if (d.is_array())
else if (JS_IsArray(d))
{
std::vector<std::string> cells;
for (const auto& dukCell : d.as_array())
{
cells.push_back(ProcessString(dukCell));
}
JSIterateArray(ctx, d, [&cells](JSContext* ctx2, JSValue x) { cells.push_back(JSToStdString(ctx2, x)); });
result = ListViewItem(std::move(cells));
}
else if (d.type() == DukValue::Type::OBJECT)
else if (JS_IsObject(d))
{
auto type = ProcessString(d["type"]);
auto type = JSToStdString(ctx, d, "type");
// This type was misspelt between 2020 and 2025.
if (type == "separator" || type == "seperator")
{
auto text = ProcessString(d["text"]);
auto text = JSToStdString(ctx, d, "text");
result = ListViewItem(text);
result.IsSeparator = true;
}
@@ -142,64 +131,51 @@ namespace OpenRCT2::Scripting
return result;
}
template<>
std::vector<ListViewColumn> FromDuk(const DukValue& d)
std::vector<ListViewColumn> ListViewColumnVecFromJS(JSContext* ctx, JSValue d)
{
std::vector<ListViewColumn> result;
if (d.is_array())
if (JS_IsArray(d))
{
auto dukColumns = d.as_array();
for (const auto& dukColumn : dukColumns)
{
result.push_back(FromDuk<ListViewColumn>(dukColumn));
}
JSIterateArray(ctx, d, [&result](JSContext* ctx2, JSValue x) { result.push_back(ListViewColumnFromJS(ctx2, x)); });
}
return result;
}
template<>
std::vector<ListViewItem> FromDuk(const DukValue& d)
std::vector<ListViewItem> ListViewItemVecFromJS(JSContext* ctx, JSValue d)
{
std::vector<ListViewItem> result;
if (d.is_array())
if (JS_IsArray(d))
{
auto dukItems = d.as_array();
for (const auto& dukItem : dukItems)
{
result.push_back(FromDuk<ListViewItem>(dukItem));
}
JSIterateArray(ctx, d, [&result](JSContext* ctx2, JSValue x) { result.push_back(ListViewItemFromJS(ctx2, x)); });
}
return result;
}
template<>
std::optional<RowColumn> FromDuk(const DukValue& d)
std::optional<RowColumn> RowColumnFromJS(JSContext* ctx, JSValue d)
{
if (d.type() == DukValue::Type::OBJECT)
if (JS_IsObject(d))
{
auto dukRow = d["row"];
auto dukColumn = d["column"];
if (dukRow.type() == DukValue::Type::NUMBER && dukColumn.type() == DukValue::Type::NUMBER)
auto row = JSToOptionalInt(ctx, d, "row");
auto column = JSToOptionalInt(ctx, d, "column");
if (row.has_value() && column.has_value())
{
return RowColumn(dukRow.as_int(), dukColumn.as_int());
return RowColumn(row.value(), column.value());
}
}
return std::nullopt;
}
template<>
DukValue ToDuk(duk_context* ctx, const RowColumn& value)
JSValue RowColumnToJS(JSContext* ctx, const RowColumn value)
{
DukObject obj(ctx);
obj.Set("row", value.Row);
obj.Set("column", value.Column);
return obj.Take();
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "row", JS_NewInt32(ctx, value.Row));
JS_SetPropertyStr(ctx, obj, "column", JS_NewInt32(ctx, value.Column));
return obj;
}
template<>
ScrollbarType FromDuk(const DukValue& d)
ScrollbarType ScrollbarTypeFromJS(JSContext* ctx, JSValue d)
{
auto value = AsOrDefault(d, "");
auto value = JSToStdString(ctx, d);
if (value == "horizontal")
return ScrollbarType::Horizontal;
if (value == "vertical")
@@ -209,20 +185,19 @@ namespace OpenRCT2::Scripting
return ScrollbarType::None;
}
template<>
DukValue ToDuk(duk_context* ctx, const ScrollbarType& value)
JSValue ScrollbarTypeToJS(JSContext* ctx, const ScrollbarType value)
{
switch (value)
{
default:
case ScrollbarType::None:
return ToDuk(ctx, "none");
return JSFromStdString(ctx, "none");
case ScrollbarType::Horizontal:
return ToDuk(ctx, "horizontal");
return JSFromStdString(ctx, "horizontal");
case ScrollbarType::Vertical:
return ToDuk(ctx, "vertical");
return JSFromStdString(ctx, "vertical");
case ScrollbarType::Both:
return ToDuk(ctx, "both");
return JSFromStdString(ctx, "both");
}
}
@@ -479,15 +454,13 @@ void CustomListView::MouseOver(const ScreenCoordsXY& pos, bool isMouseDown)
HighlightedCell = hitResult;
if (HighlightedCell != LastHighlightedCell)
{
if (hitResult->Row != kHeaderRow && OnHighlight.context() != nullptr && OnHighlight.is_function())
if (hitResult->Row != kHeaderRow && OnHighlight.context != nullptr && OnHighlight.IsValid())
{
auto ctx = OnHighlight.context();
duk_push_int(ctx, static_cast<int32_t>(HighlightedCell->Row));
auto dukRow = DukValue::take_from_stack(ctx, -1);
duk_push_int(ctx, static_cast<int32_t>(HighlightedCell->Column));
auto dukColumn = DukValue::take_from_stack(ctx, -1);
auto ctx = OnHighlight.context;
JSValue jsRow = JS_NewInt32(ctx, HighlightedCell->Row);
JSValue jsColumn = JS_NewInt32(ctx, HighlightedCell->Column);
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.ExecutePluginCall(Owner, OnHighlight, { dukRow, dukColumn }, false);
scriptEngine.ExecutePluginCall(Owner, OnHighlight.callback, { jsRow, jsColumn }, false);
}
Invalidate();
}
@@ -526,15 +499,13 @@ void CustomListView::MouseDown(const ScreenCoordsXY& pos)
Invalidate();
}
auto ctx = OnClick.context();
if (ctx != nullptr && OnClick.is_function())
auto ctx = OnClick.context;
if (ctx != nullptr && OnClick.IsValid())
{
duk_push_int(ctx, static_cast<int32_t>(hitResult->Row));
auto dukRow = DukValue::take_from_stack(ctx, -1);
duk_push_int(ctx, static_cast<int32_t>(hitResult->Column));
auto dukColumn = DukValue::take_from_stack(ctx, -1);
JSValue jsRow = JS_NewInt32(ctx, hitResult->Row);
JSValue jsColumn = JS_NewInt32(ctx, hitResult->Column);
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.ExecutePluginCall(Owner, OnClick, { dukRow, dukColumn }, false);
scriptEngine.ExecutePluginCall(Owner, OnClick.callback, { jsRow, jsColumn }, false);
}
}
}
+12 -28
View File
@@ -15,7 +15,6 @@
#include <cstdint>
#include <memory>
#include <openrct2/scripting/Duktape.hpp>
#include <openrct2/scripting/ScriptEngine.h>
#include <optional>
#include <string>
@@ -119,8 +118,8 @@ namespace OpenRCT2::Ui::Windows
bool IsMouseDown{};
bool CanSelect{};
DukValue OnClick;
DukValue OnHighlight;
JSCallback OnClick;
JSCallback OnHighlight;
CustomListView(WindowBase* parent, size_t scrollIndex);
ScrollbarType GetScrollbars() const;
@@ -155,44 +154,29 @@ namespace OpenRCT2::Ui::Windows
};
} // namespace OpenRCT2::Ui::Windows
class DukValue;
namespace OpenRCT2::Scripting
{
using namespace OpenRCT2::Ui::Windows;
template<>
ColumnSortOrder FromDuk(const DukValue& d);
ColumnSortOrder ColumnSortOrderFromJS(JSContext* ctx, JSValue d);
template<>
std::optional<int32_t> FromDuk(const DukValue& d);
ListViewColumn ListViewColumnFromJS(JSContext* ctx, JSValue d);
template<>
ListViewColumn FromDuk(const DukValue& d);
ListViewItem ListViewItemFromJS(JSContext* ctx, JSValue d);
template<>
ListViewItem FromDuk(const DukValue& d);
std::vector<ListViewColumn> ListViewColumnVecFromJS(JSContext* ctx, JSValue d);
template<>
std::vector<ListViewColumn> FromDuk(const DukValue& d);
std::vector<ListViewItem> ListViewItemVecFromJS(JSContext* ctx, JSValue d);
template<>
std::vector<ListViewItem> FromDuk(const DukValue& d);
std::optional<RowColumn> RowColumnFromJS(JSContext* ctx, JSValue d);
template<>
std::optional<RowColumn> FromDuk(const DukValue& d);
JSValue RowColumnToJS(JSContext* ctx, RowColumn value);
template<>
DukValue ToDuk(duk_context* ctx, const RowColumn& value);
JSValue ListViewColumnToJS(JSContext* ctx, const ListViewColumn& value);
template<>
DukValue ToDuk(duk_context* ctx, const ListViewColumn& value);
ScrollbarType ScrollbarTypeFromJS(JSContext* ctx, JSValue d);
template<>
ScrollbarType FromDuk(const DukValue& d);
template<>
DukValue ToDuk(duk_context* ctx, const ScrollbarType& value);
JSValue ScrollbarTypeToJS(JSContext* ctx, ScrollbarType value);
} // namespace OpenRCT2::Scripting
#endif
+115 -99
View File
@@ -16,6 +16,8 @@
#include <openrct2-ui/UiContext.h>
#include <openrct2-ui/input/ShortcutManager.h>
#include <openrct2/Input.h>
#include <openrct2/core/EnumMap.hpp>
#include <openrct2/scripting/ScriptUtil.hpp>
#include <openrct2/ui/UiContext.h>
#include <openrct2/ui/WindowManager.h>
#include <openrct2/world/Map.h>
@@ -31,7 +33,7 @@ namespace OpenRCT2::Scripting
CustomShortcut::CustomShortcut(
std::shared_ptr<Plugin> owner, std::string_view id, std::string_view text, const std::vector<std::string>& bindings,
DukValue callback)
JSCallback callback)
: Owner(owner)
, Id(id)
, Text(text)
@@ -59,7 +61,7 @@ namespace OpenRCT2::Scripting
void CustomShortcut::Invoke() const
{
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.ExecutePluginCall(Owner, Callback, {}, false);
scriptEngine.ExecutePluginCall(Owner, Callback.callback, {}, false);
}
static constexpr std::array<std::string_view, EnumValue(CursorID::Count)> CursorNames = {
@@ -69,47 +71,57 @@ namespace OpenRCT2::Scripting
"volcano_down", "walk_down", "paint_down", "entrance_down", "hand_open", "hand_closed",
};
static const DukEnumMap<ViewportInteractionItem> ToolFilterMap(
{
{ "terrain", ViewportInteractionItem::terrain },
{ "entity", ViewportInteractionItem::entity },
{ "ride", ViewportInteractionItem::ride },
{ "water", ViewportInteractionItem::water },
{ "scenery", ViewportInteractionItem::scenery },
{ "footpath", ViewportInteractionItem::footpath },
{ "footpath_item", ViewportInteractionItem::pathAddition },
{ "park_entrance", ViewportInteractionItem::parkEntrance },
{ "wall", ViewportInteractionItem::wall },
{ "large_scenery", ViewportInteractionItem::largeScenery },
{ "label", ViewportInteractionItem::label },
{ "banner", ViewportInteractionItem::banner },
});
template<>
DukValue ToDuk(duk_context* ctx, const CursorID& cursorId)
JSValue CursorIDToJSValue(JSContext* ctx, CursorID id)
{
auto value = EnumValue(cursorId);
if (value < std::size(CursorNames))
auto idVal = EnumValue(id);
if (idVal < CursorNames.size())
{
auto str = CursorNames[value];
duk_push_lstring(ctx, str.data(), str.size());
return DukValue::take_from_stack(ctx);
return JSFromStdString(ctx, CursorNames[idVal]);
}
return ToDuk(ctx, undefined);
return JS_UNDEFINED;
}
template<>
CursorID FromDuk(const DukValue& s)
static CursorID CursorJSValToID(JSContext* ctx, JSValue value)
{
if (s.type() == DukValue::Type::STRING)
if (JS_IsString(value))
{
auto it = std::find(std::begin(CursorNames), std::end(CursorNames), s.as_c_string());
if (it != std::end(CursorNames))
std::string valueStr = JSToStdString(ctx, value);
for (uint8_t i = 0; i < EnumValue(CursorID::Count); i++)
{
return static_cast<CursorID>(std::distance(std::begin(CursorNames), it));
if (CursorNames[i] == valueStr)
{
return static_cast<CursorID>(i);
}
}
}
return CursorID::Undefined;
return CursorID::Arrow;
}
static const EnumMap<ViewportInteractionItem> ToolFilterMap{
{ "terrain", ViewportInteractionItem::terrain },
{ "entity", ViewportInteractionItem::entity },
{ "ride", ViewportInteractionItem::ride },
{ "water", ViewportInteractionItem::water },
{ "scenery", ViewportInteractionItem::scenery },
{ "footpath", ViewportInteractionItem::footpath },
{ "footpath_item", ViewportInteractionItem::pathAddition },
{ "park_entrance", ViewportInteractionItem::parkEntrance },
{ "wall", ViewportInteractionItem::wall },
{ "large_scenery", ViewportInteractionItem::largeScenery },
{ "label", ViewportInteractionItem::label },
{ "banner", ViewportInteractionItem::banner },
};
static ViewportInteractionItem FilterJSValToEnum(JSContext* ctx, JSValue value)
{
if (JS_IsString(value))
{
if (auto val = ToolFilterMap.TryGet(JSToStdString(ctx, value)); val.has_value())
{
return val.value();
}
}
return ViewportInteractionItem::none;
}
static void RemoveMenuItemsAndTool(std::shared_ptr<Plugin> owner)
@@ -157,13 +169,13 @@ namespace OpenRCT2::Scripting
void CustomTool::OnUpdate(const ScreenCoordsXY& screenCoords)
{
InvokeEventHandler(onMove, screenCoords);
InvokeEventHandler(onMove.callback, screenCoords);
}
void CustomTool::OnDown(const ScreenCoordsXY& screenCoords)
{
MouseDown = true;
InvokeEventHandler(onDown, screenCoords);
InvokeEventHandler(onDown.callback, screenCoords);
}
void CustomTool::OnDrag(const ScreenCoordsXY& screenCoords)
@@ -173,36 +185,36 @@ namespace OpenRCT2::Scripting
void CustomTool::Start()
{
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.ExecutePluginCall(Owner, onStart, {}, false);
scriptEngine.ExecutePluginCall(Owner, onStart.callback, {}, false);
}
void CustomTool::OnUp(const ScreenCoordsXY& screenCoords)
{
MouseDown = false;
InvokeEventHandler(onUp, screenCoords);
InvokeEventHandler(onUp.callback, screenCoords);
}
void CustomTool::OnAbort()
{
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.ExecutePluginCall(Owner, onFinish, {}, false);
scriptEngine.ExecutePluginCall(Owner, onFinish.callback, {}, false);
}
void CustomTool::InvokeEventHandler(const DukValue& dukHandler, const ScreenCoordsXY& screenCoords)
void CustomTool::InvokeEventHandler(JSValue handler, const ScreenCoordsXY& screenCoords)
{
if (dukHandler.is_function())
JSContext* ctx = Owner->GetContext();
if (JS_IsFunction(ctx, handler))
{
auto ctx = dukHandler.context();
auto info = GetMapCoordinatesFromPos(screenCoords, Filter);
DukObject obj(dukHandler.context());
obj.Set("isDown", MouseDown);
obj.Set("screenCoords", ToDuk(ctx, screenCoords));
obj.Set("mapCoords", ToDuk(ctx, info.Loc));
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "isDown", JS_NewBool(ctx, MouseDown));
JS_SetPropertyStr(ctx, obj, "screenCoords", ToJSValue(ctx, screenCoords));
JS_SetPropertyStr(ctx, obj, "mapCoords", ToJSValue(ctx, info.Loc));
if (info.interactionType == ViewportInteractionItem::entity && info.Entity != nullptr)
{
obj.Set("entityId", info.Entity->Id.ToUnderlying());
JS_SetPropertyStr(ctx, obj, "entityId", JS_NewInt32(ctx, info.Entity->Id.ToUnderlying()));
}
else if (info.Element != nullptr)
{
@@ -214,7 +226,7 @@ namespace OpenRCT2::Scripting
{
if (el == info.Element)
{
obj.Set("tileElementIndex", index);
JS_SetPropertyStr(ctx, obj, "tileElementIndex", JS_NewInt32(ctx, index));
break;
}
index++;
@@ -223,71 +235,75 @@ namespace OpenRCT2::Scripting
}
auto& scriptEngine = GetContext()->GetScriptEngine();
std::vector<DukValue> args;
args.emplace_back(obj.Take());
scriptEngine.ExecutePluginCall(Owner, dukHandler, args, false);
scriptEngine.ExecutePluginCall(Owner, handler, { obj }, false);
}
}
void InitialiseCustomTool(ScriptEngine& scriptEngine, const DukValue& dukValue)
[[nodiscard]] JSValue InitialiseCustomTool(ScriptEngine& scriptEngine, JSContext* ctx, JSValue value)
{
try
std::shared_ptr<Plugin> currentPlugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
if (JS_IsObject(value))
{
if (dukValue.type() == DukValue::Type::OBJECT)
JSValue idVal = JS_GetPropertyStr(ctx, value, "id");
if (!JS_IsString(idVal))
{
CustomTool customTool;
customTool.Owner = scriptEngine.GetExecInfo().GetCurrentPlugin();
customTool.Id = dukValue["id"].as_string();
customTool.Cursor = FromDuk<CursorID>(dukValue["cursor"]);
if (customTool.Cursor == CursorID::Undefined)
{
customTool.Cursor = CursorID::Arrow;
}
JS_FreeValue(ctx, idVal);
JS_ThrowTypeError(ctx, "Invalid id. Expected string");
return JS_EXCEPTION;
}
auto dukFilter = dukValue["filter"];
if (dukFilter.is_array())
{
customTool.Filter = 0;
auto dukItems = dukFilter.as_array();
for (const auto& dukItem : dukItems)
{
if (dukItem.type() == DukValue::Type::STRING)
{
auto value = ToolFilterMap[dukItem.as_string()];
customTool.Filter |= static_cast<uint32_t>(EnumToFlag(value));
}
}
}
else
{
customTool.Filter = kViewportInteractionItemAll;
}
CustomTool customTool;
customTool.Owner = currentPlugin;
customTool.onStart = dukValue["onStart"];
customTool.onDown = dukValue["onDown"];
customTool.onMove = dukValue["onMove"];
customTool.onUp = dukValue["onUp"];
customTool.onFinish = dukValue["onFinish"];
customTool.Id = JSToStdString(ctx, idVal);
JS_FreeValue(ctx, idVal);
auto* windowMgr = GetWindowManager();
auto toolbarWindow = windowMgr->FindByClass(WindowClass::topToolbar);
if (toolbarWindow != nullptr)
JSValue cursorVal = JS_GetPropertyStr(ctx, value, "cursor");
customTool.Cursor = CursorJSValToID(ctx, cursorVal);
JS_FreeValue(ctx, cursorVal);
JSValue filter = JS_GetPropertyStr(ctx, value, "filter");
if (JS_IsArray(filter))
{
customTool.Filter = 0;
int64_t len = -1;
JS_GetLength(ctx, filter, &len);
for (int64_t i = 0; i < len; i++)
{
// Use a widget that does not exist on top toolbar but also make sure it isn't
// kWidgetIndexNull, as that prevents abort from being called.
// TODO: refactor this to not leech on the top toolbar.
WidgetIndex widgetIndex = 0xFFFE;
ToolCancel();
ToolSet(*toolbarWindow, widgetIndex, static_cast<Tool>(customTool.Cursor));
ActiveCustomTool = std::move(customTool);
ActiveCustomTool->Start();
JSValue curFilter = JS_GetPropertyInt64(ctx, filter, i);
auto elem = FilterJSValToEnum(ctx, curFilter);
customTool.Filter |= static_cast<uint32_t>(EnumToFlag(elem));
JS_FreeValue(ctx, curFilter);
}
}
else
{
customTool.Filter = kViewportInteractionItemAll;
}
JS_FreeValue(ctx, filter);
customTool.onStart = JSToCallback(ctx, value, "onStart");
customTool.onDown = JSToCallback(ctx, value, "onDown");
customTool.onMove = JSToCallback(ctx, value, "onMove");
customTool.onUp = JSToCallback(ctx, value, "onUp");
customTool.onFinish = JSToCallback(ctx, value, "onFinish");
auto* windowMgr = GetWindowManager();
auto toolbarWindow = windowMgr->FindByClass(WindowClass::topToolbar);
if (toolbarWindow != nullptr)
{
// Use a widget that does not exist on top toolbar but also make sure it isn't
// kWidgetIndexNull, as that prevents abort from being called.
// TODO: refactor this to not leech on the top toolbar.
WidgetIndex widgetIndex = 0xFFFE;
ToolCancel();
ToolSet(*toolbarWindow, widgetIndex, static_cast<Tool>(customTool.Cursor));
ActiveCustomTool = std::move(customTool);
ActiveCustomTool->Start();
}
}
catch (const DukException&)
{
duk_error(scriptEngine.GetContext(), DUK_ERR_ERROR, "Invalid parameters.");
}
return JS_UNDEFINED;
}
} // namespace OpenRCT2::Scripting
+13 -18
View File
@@ -14,7 +14,6 @@
#include <memory>
#include <openrct2/Context.h>
#include <openrct2/interface/Cursors.h>
#include <openrct2/scripting/Duktape.hpp>
#include <openrct2/scripting/ScriptEngine.h>
#include <string>
#include <vector>
@@ -35,10 +34,10 @@ namespace OpenRCT2::Scripting
std::shared_ptr<Plugin> Owner;
CustomToolbarMenuItemKind Kind;
std::string Text;
DukValue Callback;
JSCallback Callback;
CustomToolbarMenuItem(
std::shared_ptr<Plugin> owner, CustomToolbarMenuItemKind kind, const std::string& text, DukValue callback)
std::shared_ptr<Plugin> owner, CustomToolbarMenuItemKind kind, const std::string& text, JSCallback callback)
: Owner(owner)
, Kind(kind)
, Text(text)
@@ -49,7 +48,7 @@ namespace OpenRCT2::Scripting
void Invoke() const
{
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.ExecutePluginCall(Owner, Callback, {}, false);
scriptEngine.ExecutePluginCall(Owner, Callback.callback, {}, false);
}
};
@@ -60,11 +59,11 @@ namespace OpenRCT2::Scripting
std::string Id;
std::string Text;
std::vector<std::string> Bindings;
DukValue Callback;
JSCallback Callback;
CustomShortcut(
std::shared_ptr<Plugin> owner, std::string_view id, std::string_view text, const std::vector<std::string>& bindings,
DukValue callback);
JSCallback callback);
CustomShortcut(CustomShortcut&&) = default;
CustomShortcut(const CustomShortcut&) = delete;
~CustomShortcut();
@@ -84,11 +83,11 @@ namespace OpenRCT2::Scripting
bool MouseDown{};
// Event handlers
DukValue onStart;
DukValue onDown;
DukValue onMove;
DukValue onUp;
DukValue onFinish;
JSCallback onStart;
JSCallback onDown;
JSCallback onMove;
JSCallback onUp;
JSCallback onFinish;
void Start();
void OnUpdate(const ScreenCoordsXY& screenCoords);
@@ -98,7 +97,7 @@ namespace OpenRCT2::Scripting
void OnAbort();
private:
void InvokeEventHandler(const DukValue& dukHandler, const ScreenCoordsXY& screenCoords);
void InvokeEventHandler(JSValue handler, const ScreenCoordsXY& screenCoords);
};
extern std::optional<CustomTool> ActiveCustomTool;
@@ -106,13 +105,9 @@ namespace OpenRCT2::Scripting
extern std::vector<std::unique_ptr<CustomShortcut>> CustomShortcuts;
void InitialiseCustomMenuItems(ScriptEngine& scriptEngine);
void InitialiseCustomTool(ScriptEngine& scriptEngine, const DukValue& dukValue);
template<>
DukValue ToDuk(duk_context* ctx, const CursorID& value);
template<>
CursorID FromDuk(const DukValue& s);
JSValue InitialiseCustomTool(ScriptEngine& scriptEngine, JSContext* ctx, JSValue value);
JSValue CursorIDToJSValue(JSContext* ctx, CursorID id);
} // namespace OpenRCT2::Scripting
#endif
+152 -170
View File
@@ -17,13 +17,14 @@
#include "../windows/Windows.h"
#include "CustomListView.h"
#include "ScUi.hpp"
#include "ScWindow.hpp"
#include "ScWindow.h"
#include <limits>
#include <openrct2/SpriteIds.h>
#include <openrct2/config/Config.h>
#include <openrct2/drawing/Drawing.h>
#include <openrct2/interface/ColourWithFlags.h>
#include <openrct2/interface/Viewport.h>
#include <openrct2/interface/Window.h>
#include <openrct2/scripting/Plugin.h>
#include <optional>
@@ -82,114 +83,118 @@ namespace OpenRCT2::Ui::Windows
bool CanSelect{};
// Event handlers
DukValue OnClick;
DukValue OnChange;
DukValue OnDraw;
DukValue OnIncrement;
DukValue OnDecrement;
DukValue OnHighlight;
JSCallback OnClick;
JSCallback OnChange;
JSCallback OnDraw;
JSCallback OnIncrement;
JSCallback OnDecrement;
JSCallback OnHighlight;
static CustomWidgetDesc FromDukValue(DukValue desc)
static CustomWidgetDesc FromJSValue(JSContext* ctx, JSValue desc)
{
CustomWidgetDesc result;
result.Type = desc["type"].as_string();
result.X = desc["x"].as_int();
result.Y = desc["y"].as_int();
result.Width = desc["width"].as_int();
result.Height = desc["height"].as_int();
result.IsDisabled = AsOrDefault(desc["isDisabled"], false);
result.IsVisible = AsOrDefault(desc["isVisible"], true);
result.Name = AsOrDefault(desc["name"], "");
result.Tooltip = AsOrDefault(desc["tooltip"], "");
result.Type = JSToStdString(ctx, desc, "type");
result.X = JSToInt(ctx, desc, "x");
result.Y = JSToInt(ctx, desc, "y");
result.Width = JSToInt(ctx, desc, "width");
result.Height = JSToInt(ctx, desc, "height");
result.IsDisabled = AsOrDefault(ctx, desc, "isDisabled", false);
result.IsVisible = AsOrDefault(ctx, desc, "isVisible", true);
result.Name = AsOrDefault(ctx, desc, "name", "");
result.Tooltip = AsOrDefault(ctx, desc, "tooltip", "");
if (result.Type == "button")
{
auto dukImage = desc["image"];
if (dukImage.type() == DukValue::Type::STRING || dukImage.type() == DukValue::Type::NUMBER)
JSValue jsImage = JS_GetPropertyStr(ctx, desc, "image");
if (JS_IsString(jsImage) || JS_IsNumber(jsImage))
{
result.Image = ImageId(ImageFromDuk(dukImage));
result.Image = ImageId(ImageFromJSValue(ctx, jsImage));
result.HasBorder = false;
}
else
{
result.Text = ProcessString(desc["text"]);
result.Text = AsOrDefault(ctx, desc, "text", "");
result.HasBorder = true;
}
result.IsPressed = AsOrDefault(desc["isPressed"], false);
result.OnClick = desc["onClick"];
JS_FreeValue(ctx, jsImage);
result.IsPressed = AsOrDefault(ctx, desc, "isPressed", false);
result.OnClick = JSToCallback(ctx, desc, "onClick");
}
else if (result.Type == "checkbox")
{
result.Text = ProcessString(desc["text"]);
result.IsChecked = AsOrDefault(desc["isChecked"], false);
result.OnChange = desc["onChange"];
result.Text = AsOrDefault(ctx, desc, "text", "");
result.IsChecked = AsOrDefault(ctx, desc, "isChecked", false);
result.OnChange = JSToCallback(ctx, desc, "onChange");
}
else if (result.Type == "colourpicker")
{
auto colour = AsOrDefault(desc["colour"], 0);
auto colour = AsOrDefault(ctx, desc, "colour", 0);
if (colour < kColourNumTotal)
{
result.Colour = static_cast<Drawing::Colour>(colour);
}
result.OnChange = desc["onChange"];
result.OnChange = JSToCallback(ctx, desc, "onChange");
}
else if (result.Type == "custom")
{
result.OnDraw = desc["onDraw"];
result.OnDraw = JSToCallback(ctx, desc, "onDraw");
}
else if (result.Type == "dropdown")
{
if (desc["items"].is_array())
{
auto dukItems = desc["items"].as_array();
for (const auto& dukItem : dukItems)
{
result.Items.push_back(ProcessString(dukItem));
}
}
result.SelectedIndex = AsOrDefault(desc["selectedIndex"], 0);
result.OnChange = desc["onChange"];
JSIterateArray(ctx, desc, "items", [&result](JSContext* ctx2, JSValue val) {
result.Items.push_back(JSToStdString(ctx2, val));
});
result.SelectedIndex = AsOrDefault(ctx, desc, "selectedIndex", 0);
result.OnChange = JSToCallback(ctx, desc, "onChange");
}
else if (result.Type == "groupbox")
{
result.Text = ProcessString(desc["text"]);
result.Text = AsOrDefault(ctx, desc, "text", "");
}
else if (result.Type == "label")
{
result.Text = ProcessString(desc["text"]);
if (ProcessString(desc["textAlign"]) == "centred")
result.Text = AsOrDefault(ctx, desc, "text", "");
if (JSToStdString(ctx, desc, "textAlign") == "centred")
{
result.TextAlign = TextAlignment::centre;
}
}
else if (result.Type == "listview")
{
result.ListViewColumns = FromDuk<std::vector<ListViewColumn>>(desc["columns"]);
result.ListViewItems = FromDuk<std::vector<ListViewItem>>(desc["items"]);
result.SelectedCell = FromDuk<std::optional<RowColumn>>(desc["selectedCell"]);
result.ShowColumnHeaders = AsOrDefault(desc["showColumnHeaders"], false);
result.IsStriped = AsOrDefault(desc["isStriped"], false);
result.OnClick = desc["onClick"];
result.OnHighlight = desc["onHighlight"];
result.CanSelect = AsOrDefault(desc["canSelect"], false);
if (desc["scrollbars"].type() == DukValue::UNDEFINED)
JSValue cols = JS_GetPropertyStr(ctx, desc, "columns");
JSValue items = JS_GetPropertyStr(ctx, desc, "items");
JSValue selected = JS_GetPropertyStr(ctx, desc, "selectedCell");
JSValue scrollbars = JS_GetPropertyStr(ctx, desc, "scrollbars");
result.ListViewColumns = ListViewColumnVecFromJS(ctx, cols);
result.ListViewItems = ListViewItemVecFromJS(ctx, items);
result.SelectedCell = RowColumnFromJS(ctx, selected);
result.ShowColumnHeaders = AsOrDefault(ctx, desc, "showColumnHeaders", false);
result.IsStriped = AsOrDefault(ctx, desc, "isStriped", false);
result.OnClick = JSToCallback(ctx, desc, "onClick");
result.OnHighlight = JSToCallback(ctx, desc, "onHighlight");
result.CanSelect = AsOrDefault(ctx, desc, "canSelect", false);
if (JS_IsUndefined(scrollbars))
result.Scrollbars = ScrollbarType::Vertical;
else
result.Scrollbars = FromDuk<ScrollbarType>(desc["scrollbars"]);
result.Scrollbars = ScrollbarTypeFromJS(ctx, scrollbars);
JS_FreeValue(ctx, cols);
JS_FreeValue(ctx, items);
JS_FreeValue(ctx, selected);
JS_FreeValue(ctx, scrollbars);
}
else if (result.Type == "spinner")
{
result.Text = ProcessString(desc["text"]);
result.OnIncrement = desc["onIncrement"];
result.OnDecrement = desc["onDecrement"];
result.OnClick = desc["onClick"];
result.Text = AsOrDefault(ctx, desc, "text", "");
result.OnIncrement = JSToCallback(ctx, desc, "onIncrement");
result.OnDecrement = JSToCallback(ctx, desc, "onDecrement");
result.OnClick = JSToCallback(ctx, desc, "onClick");
}
else if (result.Type == "textbox")
{
result.Text = ProcessString(desc["text"]);
result.MaxLength = AsOrDefault(desc["maxLength"], 32);
result.OnChange = desc["onChange"];
result.Text = AsOrDefault(ctx, desc, "text", "");
result.MaxLength = AsOrDefault(ctx, desc, "maxLength", 32);
result.OnChange = JSToCallback(ctx, desc, "onChange");
}
result.HasBorder = AsOrDefault(desc["border"], result.HasBorder);
result.HasBorder = AsOrDefault(ctx, desc, "border", result.HasBorder);
return result;
}
};
@@ -202,53 +207,53 @@ namespace OpenRCT2::Ui::Windows
ScreenCoordsXY offset;
std::vector<CustomWidgetDesc> Widgets;
static CustomTabDesc FromDukValue(const DukValue& desc)
static CustomTabDesc FromJSValue(JSContext* ctx, JSValue desc)
{
CustomTabDesc result;
auto dukImage = desc["image"];
if (dukImage.type() == DukValue::Type::STRING || dukImage.type() == DukValue::Type::NUMBER)
JSValue jsImage = JS_GetPropertyStr(ctx, desc, "image");
if (JS_IsString(jsImage) || JS_IsNumber(jsImage))
{
result.imageFrameBase = ImageId(ImageFromDuk(dukImage));
result.imageFrameBase = ImageId(ImageFromJSValue(ctx, jsImage));
result.imageFrameCount = 0;
result.imageFrameDuration = 0;
}
else if (dukImage.type() == DukValue::Type::OBJECT)
else if (JS_IsObject(jsImage))
{
result.imageFrameBase = ImageId(dukImage["frameBase"].as_uint());
result.imageFrameCount = AsOrDefault(dukImage["frameCount"], 0);
result.imageFrameDuration = AsOrDefault(dukImage["frameDuration"], 0);
result.imageFrameBase = ImageId(JSToInt64(ctx, jsImage, "frameBase"));
result.imageFrameCount = AsOrDefault(ctx, jsImage, "frameCount", 0);
result.imageFrameDuration = AsOrDefault(ctx, jsImage, "frameDuration", 0);
if (dukImage["primaryColour"].type() == DukValue::Type::NUMBER)
auto primaryCol = JSToOptionalInt64(ctx, jsImage, "primaryColour");
if (primaryCol.has_value())
{
result.imageFrameBase = result.imageFrameBase.WithPrimary(
static_cast<Colour>(dukImage["primaryColour"].as_uint()));
result.imageFrameBase = result.imageFrameBase.WithPrimary(static_cast<Colour>(primaryCol.value()));
if (dukImage["secondaryColour"].type() == DukValue::Type::NUMBER)
auto secondaryCol = JSToOptionalInt64(ctx, jsImage, "secondaryColour");
if (secondaryCol.has_value())
{
result.imageFrameBase = result.imageFrameBase.WithSecondary(
static_cast<Colour>(dukImage["secondaryColour"].as_uint()));
result.imageFrameBase = result.imageFrameBase.WithSecondary(static_cast<Colour>(secondaryCol.value()));
if (dukImage["tertiaryColour"].type() == DukValue::Type::NUMBER)
auto tertiaryCol = JSToOptionalInt64(ctx, jsImage, "tertiaryColour");
if (tertiaryCol.has_value())
{
result.imageFrameBase = result.imageFrameBase.WithTertiary(
static_cast<Colour>(dukImage["tertiaryColour"].as_uint()));
static_cast<Colour>(tertiaryCol.value()));
}
}
}
auto dukCoord = dukImage["offset"];
if (dukCoord.type() == DukValue::Type::OBJECT)
JSValue jsCoord = JS_GetPropertyStr(ctx, jsImage, "offset");
if (JS_IsObject(jsCoord))
{
result.offset = { AsOrDefault(dukCoord["x"], 0), AsOrDefault(dukCoord["y"], 0) };
result.offset = { AsOrDefault(ctx, jsCoord, "x", 0), AsOrDefault(ctx, jsCoord, "y", 0) };
}
JS_FreeValue(ctx, jsCoord);
}
if (desc["widgets"].is_array())
{
auto dukWidgets = desc["widgets"].as_array();
std::transform(dukWidgets.begin(), dukWidgets.end(), std::back_inserter(result.Widgets), [](const DukValue& w) {
return CustomWidgetDesc::FromDukValue(w);
});
}
JS_FreeValue(ctx, jsImage);
JSIterateArray(ctx, desc, "widgets", [&result](JSContext* ctx2, JSValue x) {
result.Widgets.push_back(CustomWidgetDesc::FromJSValue(ctx2, x));
});
return result;
}
};
@@ -271,9 +276,9 @@ namespace OpenRCT2::Ui::Windows
std::optional<int32_t> TabIndex;
// Event handlers
DukValue OnClose;
DukValue OnUpdate;
DukValue OnTabChange;
JSCallback OnClose;
JSCallback OnUpdate;
JSCallback OnTabChange;
CustomWindowDesc() = default;
@@ -282,65 +287,49 @@ namespace OpenRCT2::Ui::Windows
return MinWidth || MinHeight || MaxWidth || MaxHeight;
}
static CustomWindowDesc FromDukValue(DukValue desc)
static CustomWindowDesc FromJSValue(JSContext* ctx, JSValue desc)
{
CustomWindowDesc result;
result.Classification = desc["classification"].as_string();
result.X = GetOptionalInt(desc["x"]);
result.Y = GetOptionalInt(desc["y"]);
result.size.width = desc["width"].as_int();
result.size.height = desc["height"].as_int();
result.MinWidth = GetOptionalInt(desc["minWidth"]);
result.MaxWidth = GetOptionalInt(desc["maxWidth"]);
result.MinHeight = GetOptionalInt(desc["minHeight"]);
result.MaxHeight = GetOptionalInt(desc["maxHeight"]);
result.Title = desc["title"].as_string();
result.Id = GetOptionalInt(desc["id"]);
result.TabIndex = GetOptionalInt(desc["tabIndex"]);
result.Classification = JSToStdString(ctx, desc, "classification");
result.X = JSToOptionalInt(ctx, desc, "x");
result.Y = JSToOptionalInt(ctx, desc, "y");
result.size.width = JSToInt(ctx, desc, "width");
result.size.height = JSToInt(ctx, desc, "height");
result.MinWidth = JSToOptionalInt(ctx, desc, "minWidth");
result.MaxWidth = JSToOptionalInt(ctx, desc, "maxWidth");
result.MinHeight = JSToOptionalInt(ctx, desc, "minHeight");
result.MaxHeight = JSToOptionalInt(ctx, desc, "maxHeight");
result.Title = JSToStdString(ctx, desc, "title");
result.Id = JSToOptionalInt(ctx, desc, "id");
result.TabIndex = JSToOptionalInt(ctx, desc, "tabIndex");
if (desc["widgets"].is_array())
{
auto dukWidgets = desc["widgets"].as_array();
std::transform(dukWidgets.begin(), dukWidgets.end(), std::back_inserter(result.Widgets), [](const DukValue& w) {
return CustomWidgetDesc::FromDukValue(w);
});
}
JSIterateArray(ctx, desc, "widgets", [&result](JSContext* ctx2, JSValue val) {
result.Widgets.push_back(CustomWidgetDesc::FromJSValue(ctx2, val));
});
if (desc["tabs"].is_array())
{
auto dukTabs = desc["tabs"].as_array();
std::transform(dukTabs.begin(), dukTabs.end(), std::back_inserter(result.Tabs), [](const DukValue& w) {
return CustomTabDesc::FromDukValue(w);
});
}
JSIterateArray(ctx, desc, "tabs", [&result](JSContext* ctx2, JSValue x) {
result.Tabs.push_back(CustomTabDesc::FromJSValue(ctx2, x));
});
if (desc["colours"].is_array())
{
auto dukColours = desc["colours"].as_array();
std::transform(dukColours.begin(), dukColours.end(), std::back_inserter(result.Colours), [](const DukValue& w) {
ColourWithFlags c = { Colour::black };
if (w.type() == DukValue::Type::NUMBER)
{
uint8_t colour = (w.as_uint() & ~kLegacyColourFlagTranslucent) % kColourNumTotal;
bool isTranslucent = (w.as_uint() & kLegacyColourFlagTranslucent);
c.colour = static_cast<Colour>(colour);
c.flags.set(ColourFlag::translucent, isTranslucent);
}
return c;
});
}
JSIterateArray(ctx, desc, "colours", [&result](JSContext* ctx2, JSValue x) {
ColourWithFlags c = { Colour::black };
if (JS_IsNumber(x))
{
int32_t xValue = JSToInt(ctx2, x);
uint8_t colour = (xValue & ~kLegacyColourFlagTranslucent) % kColourNumTotal;
bool isTranslucent = (xValue & kLegacyColourFlagTranslucent);
c.colour = static_cast<Colour>(colour);
c.flags.set(ColourFlag::translucent, isTranslucent);
}
result.Colours.push_back(c);
});
result.OnClose = desc["onClose"];
result.OnUpdate = desc["onUpdate"];
result.OnTabChange = desc["onTabChange"];
result.OnClose = JSToCallback(ctx, desc, "onClose");
result.OnUpdate = JSToCallback(ctx, desc, "onUpdate");
result.OnTabChange = JSToCallback(ctx, desc, "onTabChange");
return result;
}
static std::optional<int32_t> GetOptionalInt(DukValue input)
{
return input.type() == DukValue::Type::NUMBER ? std::make_optional(input.as_int()) : std::nullopt;
}
};
class CustomWindowInfo
@@ -392,9 +381,9 @@ namespace OpenRCT2::Ui::Windows
class CustomWindow;
static CustomWindowInfo& GetInfo(CustomWindow* w);
static void InvokeEventHandler(const std::shared_ptr<Plugin>& owner, const DukValue& dukHandler);
static void InvokeEventHandler(const std::shared_ptr<Plugin>& owner, const JSCallback& jsCallback);
static void InvokeEventHandler(
const std::shared_ptr<Plugin>& owner, const DukValue& dukHandler, const std::vector<DukValue>& args);
const std::shared_ptr<Plugin>& owner, const JSCallback& jsCallback, const std::vector<JSValue>& args);
class CustomWindow final : public Window
{
@@ -559,18 +548,19 @@ namespace OpenRCT2::Ui::Windows
if (widgetDesc != nullptr && widgetDesc->Type == "custom")
{
auto& onDraw = widgetDesc->OnDraw;
if (onDraw.is_function())
if (onDraw.IsValid())
{
RenderTarget widgetRT;
if (ClipRenderTarget(
widgetRT, rt, { windowPos.x + widget.left, windowPos.y + widget.top }, widget.width() - 1,
widget.height() - 1))
{
auto ctx = onDraw.context();
auto dukWidget = ScWidget::ToDukValue(ctx, this, widgetIndex);
auto dukG = GetObjectAsDukValue(ctx, std::make_shared<ScGraphicsContext>(ctx, widgetRT));
auto ctx = onDraw.context;
auto jsWidget = gScWidget.New(ctx, this, widgetIndex);
auto gfx = gScGraphicsContext.New(ctx, widgetRT);
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.ExecutePluginCall(_info.Owner, widgetDesc->OnDraw, dukWidget, { dukG }, false);
scriptEngine.ExecutePluginCall(_info.Owner, onDraw.callback, jsWidget, { gfx }, false);
JS_FreeValue(ctx, jsWidget);
}
}
}
@@ -614,10 +604,8 @@ namespace OpenRCT2::Ui::Windows
widgetSetCheckboxValue(*this, widgetIndex, isChecked);
std::vector<DukValue> args;
auto ctx = widgetDesc->OnChange.context();
duk_push_boolean(ctx, isChecked);
args.push_back(DukValue::take_from_stack(ctx));
std::vector<JSValue> args;
args.push_back(JS_NewBool(widgetDesc->OnChange.context, isChecked));
InvokeEventHandler(_info.Owner, widgetDesc->OnChange, args);
}
else if (widgetDesc->Type == "spinner")
@@ -707,10 +695,9 @@ namespace OpenRCT2::Ui::Windows
{
UpdateWidgetText(this, widgetIndex, text);
std::vector<DukValue> args;
auto ctx = widgetDesc->OnChange.context();
duk_push_lstring(ctx, text.data(), text.size());
args.push_back(DukValue::take_from_stack(ctx));
std::vector<JSValue> args;
std::string textStr(text);
args.push_back(JSFromStdString(widgetDesc->OnChange.context, textStr));
InvokeEventHandler(_info.Owner, widgetDesc->OnChange, args);
}
}
@@ -1127,9 +1114,9 @@ namespace OpenRCT2::Ui::Windows
WindowNumber CustomWindow::_nextWindowNumber;
WindowBase* WindowCustomOpen(std::shared_ptr<Plugin> owner, DukValue dukDesc)
WindowBase* WindowCustomOpen(JSContext* ctx, std::shared_ptr<Plugin> owner, JSValue descVal)
{
auto desc = CustomWindowDesc::FromDukValue(dukDesc);
auto desc = CustomWindowDesc::FromJSValue(ctx, descVal);
WindowFlags windowFlags = { WindowFlag::resizable, WindowFlag::transparent };
auto* windowMgr = GetWindowManager();
@@ -1151,17 +1138,17 @@ namespace OpenRCT2::Ui::Windows
return w->getInfo();
}
static void InvokeEventHandler(const std::shared_ptr<Plugin>& owner, const DukValue& dukHandler)
static void InvokeEventHandler(const std::shared_ptr<Plugin>& owner, const JSCallback& jsCallback)
{
std::vector<DukValue> args;
InvokeEventHandler(owner, dukHandler, args);
std::vector<JSValue> args;
InvokeEventHandler(owner, jsCallback, args);
}
static void InvokeEventHandler(
const std::shared_ptr<Plugin>& owner, const DukValue& dukHandler, const std::vector<DukValue>& args)
const std::shared_ptr<Plugin>& owner, const JSCallback& jsCallback, const std::vector<JSValue>& args)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.ExecutePluginCall(owner, dukHandler, args, false);
scriptEngine.ExecutePluginCall(owner, jsCallback.callback, args, false);
}
std::string GetWindowTitle(WindowBase* w)
@@ -1245,10 +1232,8 @@ namespace OpenRCT2::Ui::Windows
auto* windowMgr = GetWindowManager();
windowMgr->InvalidateWidget(*w, widgetIndex);
std::vector<DukValue> args;
auto ctx = customWidgetInfo->OnChange.context();
duk_push_int(ctx, EnumValue(colour));
args.push_back(DukValue::take_from_stack(ctx));
std::vector<JSValue> args;
args.push_back(JS_NewInt32(customWidgetInfo->OnChange.context, EnumValue(colour)));
InvokeEventHandler(customInfo.Owner, customWidgetInfo->OnChange, args);
}
}
@@ -1293,10 +1278,8 @@ namespace OpenRCT2::Ui::Windows
if (lastSelectedIndex != selectedIndex)
{
std::vector<DukValue> args;
auto ctx = customWidgetInfo->OnChange.context();
duk_push_int(ctx, selectedIndex);
args.push_back(DukValue::take_from_stack(ctx));
std::vector<JSValue> args;
args.push_back(JS_NewInt32(customWidgetInfo->OnChange.context, selectedIndex));
InvokeEventHandler(customInfo.Owner, customWidgetInfo->OnChange, args);
}
}
@@ -1499,7 +1482,6 @@ namespace OpenRCT2::Ui::Windows
windowMgr->Close(*window);
}
}
} // namespace OpenRCT2::Ui::Windows
#endif
+2 -3
View File
@@ -11,8 +11,6 @@
#ifdef ENABLE_SCRIPTING
#include "../interface/Window.h"
#include <memory>
#include <optional>
#include <string_view>
@@ -40,7 +38,8 @@ namespace OpenRCT2::Ui::Windows
CustomListView* GetCustomListView(WindowBase* w, WidgetIndex widgetIndex);
int32_t GetWidgetMaxLength(WindowBase* w, WidgetIndex widgetIndex);
void SetWidgetMaxLength(WindowBase* w, WidgetIndex widgetIndex, int32_t value);
void CloseWindowsOwnedByPlugin(std::shared_ptr<Plugin> plugin);
void CloseWindowsOwnedByPlugin(std::shared_ptr<Scripting::Plugin> plugin);
WindowBase* WindowCustomOpen(JSContext* ctx, std::shared_ptr<Scripting::Plugin> owner, JSValue descVal);
} // namespace OpenRCT2::Ui::Windows
#endif
+196 -105
View File
@@ -18,230 +18,321 @@
#include <openrct2/drawing/Rectangle.h>
#include <openrct2/drawing/RenderTarget.h>
#include <openrct2/drawing/Text.h>
#include <openrct2/scripting/Duktape.hpp>
#include <openrct2/scripting/ScriptEngine.h>
#include <quickjs.h>
namespace OpenRCT2::Scripting
{
class ScGraphicsContext
class ScGraphicsContext;
extern ScGraphicsContext gScGraphicsContext;
class ScGraphicsContext final : public ScBase
{
private:
duk_context* _ctx{};
Drawing::RenderTarget _rt{};
struct GraphicsData
{
Drawing::RenderTarget _rt{};
std::optional<uint8_t> _colour{};
std::optional<uint8_t> _secondaryColour{};
std::optional<uint8_t> _tertiaryColour{};
std::optional<uint8_t> _paletteId{};
Drawing::PaletteIndex _stroke{};
Drawing::PaletteIndex _fill{};
std::optional<uint8_t> _colour{};
std::optional<uint8_t> _secondaryColour{};
std::optional<uint8_t> _tertiaryColour{};
std::optional<uint8_t> _paletteId{};
Drawing::PaletteIndex _stroke{};
Drawing::PaletteIndex _fill{};
};
public:
ScGraphicsContext(duk_context* ctx, const Drawing::RenderTarget& rt)
: _ctx(ctx)
, _rt(rt)
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "GraphicsContext", Finalize);
}
static void Register(duk_context* ctx)
static void Finalize(JSRuntime* rt, JSValue thisVal)
{
dukglue_register_property(ctx, &ScGraphicsContext::colour_get, &ScGraphicsContext::colour_set, "colour");
dukglue_register_property(
ctx, &ScGraphicsContext::secondaryColour_get, &ScGraphicsContext::secondaryColour_set, "secondaryColour");
dukglue_register_property(
ctx, &ScGraphicsContext::tertiaryColour_get, &ScGraphicsContext::tertiaryColour_set, "ternaryColour");
dukglue_register_property(
ctx, &ScGraphicsContext::tertiaryColour_get, &ScGraphicsContext::tertiaryColour_set, "tertiaryColour");
dukglue_register_property(ctx, &ScGraphicsContext::paletteId_get, &ScGraphicsContext::paletteId_set, "paletteId");
dukglue_register_property(ctx, &ScGraphicsContext::fill_get, &ScGraphicsContext::fill_set, "fill");
dukglue_register_property(ctx, &ScGraphicsContext::stroke_get, &ScGraphicsContext::stroke_set, "stroke");
dukglue_register_property(ctx, &ScGraphicsContext::width_get, nullptr, "width");
dukglue_register_property(ctx, &ScGraphicsContext::height_get, nullptr, "height");
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
if (data)
delete data;
}
dukglue_register_method(ctx, &ScGraphicsContext::getImage, "getImage");
dukglue_register_method(ctx, &ScGraphicsContext::measureText, "measureText");
JSValue New(JSContext* ctx, const Drawing::RenderTarget& rt)
{
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("colour", ScGraphicsContext::colour_get, ScGraphicsContext::colour_set),
JS_CGETSET_DEF(
"secondaryColour", ScGraphicsContext::secondaryColour_get, ScGraphicsContext::secondaryColour_set),
JS_CGETSET_DEF("ternaryColour", ScGraphicsContext::tertiaryColour_get, ScGraphicsContext::tertiaryColour_set),
JS_CGETSET_DEF("tertiaryColour", ScGraphicsContext::tertiaryColour_get, ScGraphicsContext::tertiaryColour_set),
JS_CGETSET_DEF("paletteId", ScGraphicsContext::paletteId_get, ScGraphicsContext::paletteId_set),
JS_CGETSET_DEF("fill", ScGraphicsContext::fill_get, ScGraphicsContext::fill_set),
JS_CGETSET_DEF("stroke", ScGraphicsContext::stroke_get, ScGraphicsContext::stroke_set),
JS_CGETSET_DEF("width", ScGraphicsContext::width_get, nullptr),
JS_CGETSET_DEF("height", ScGraphicsContext::height_get, nullptr),
dukglue_register_method(ctx, &ScGraphicsContext::box, "box");
dukglue_register_method(ctx, &ScGraphicsContext::clear, "clear");
dukglue_register_method(ctx, &ScGraphicsContext::clip, "clip");
dukglue_register_method(ctx, &ScGraphicsContext::image, "image");
dukglue_register_method(ctx, &ScGraphicsContext::line, "line");
dukglue_register_method(ctx, &ScGraphicsContext::rect, "rect");
dukglue_register_method(ctx, &ScGraphicsContext::text, "text");
dukglue_register_method(ctx, &ScGraphicsContext::well, "well");
JS_CFUNC_DEF("getImage", 1, ScGraphicsContext::getImage),
JS_CFUNC_DEF("measureText", 1, ScGraphicsContext::measureText),
JS_CFUNC_DEF("box", 4, ScGraphicsContext::box),
JS_CFUNC_DEF("clear", 0, ScGraphicsContext::clear),
JS_CFUNC_DEF("clip", 4, ScGraphicsContext::clip),
JS_CFUNC_DEF("image", 3, ScGraphicsContext::image),
JS_CFUNC_DEF("line", 4, ScGraphicsContext::lineJS),
JS_CFUNC_DEF("rect", 4, ScGraphicsContext::rect),
JS_CFUNC_DEF("text", 3, ScGraphicsContext::text),
JS_CFUNC_DEF("well", 4, ScGraphicsContext::well),
};
return MakeWithOpaque(ctx, funcs, new GraphicsData{ rt });
}
private:
DukValue colour_get() const
static JSValue colour_get(JSContext* ctx, JSValue thisVal)
{
return ToDuk(_ctx, _colour);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
return ToJSValue(ctx, data->_colour);
}
void colour_set(DukValue value)
static JSValue colour_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
if (value.type() == DukValue::NUMBER)
_colour = static_cast<uint8_t>(value.as_uint());
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
if (JS_IsNumber(value))
data->_colour = static_cast<uint8_t>(JSToInt(ctx, value));
else
_colour = {};
data->_colour = {};
return JS_UNDEFINED;
}
DukValue secondaryColour_get() const
static JSValue secondaryColour_get(JSContext* ctx, JSValue thisVal)
{
return ToDuk(_ctx, _secondaryColour);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
return ToJSValue(ctx, data->_secondaryColour);
}
void secondaryColour_set(DukValue value)
static JSValue secondaryColour_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
if (value.type() == DukValue::NUMBER)
_secondaryColour = static_cast<uint8_t>(value.as_uint());
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
if (JS_IsNumber(value))
data->_secondaryColour = static_cast<uint8_t>(JSToInt(ctx, value));
else
_secondaryColour = {};
data->_secondaryColour = {};
return JS_UNDEFINED;
}
DukValue tertiaryColour_get() const
static JSValue tertiaryColour_get(JSContext* ctx, JSValue thisVal)
{
return ToDuk(_ctx, _tertiaryColour);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
return ToJSValue(ctx, data->_tertiaryColour);
}
void tertiaryColour_set(DukValue value)
static JSValue tertiaryColour_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
if (value.type() == DukValue::NUMBER)
_tertiaryColour = static_cast<uint8_t>(value.as_uint());
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
if (JS_IsNumber(value))
data->_tertiaryColour = static_cast<uint8_t>(JSToInt(ctx, value));
else
_tertiaryColour = {};
data->_tertiaryColour = {};
return JS_UNDEFINED;
}
DukValue paletteId_get() const
static JSValue paletteId_get(JSContext* ctx, JSValue thisVal)
{
return ToDuk(_ctx, _paletteId);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
return ToJSValue(ctx, data->_paletteId);
}
void paletteId_set(DukValue value)
static JSValue paletteId_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
if (value.type() == DukValue::NUMBER)
_paletteId = static_cast<uint8_t>(value.as_uint());
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
if (JS_IsNumber(value))
data->_paletteId = static_cast<uint8_t>(JSToInt(ctx, value));
else
_paletteId = {};
data->_paletteId = {};
return JS_UNDEFINED;
}
uint8_t fill_get() const
static JSValue fill_get(JSContext* ctx, JSValue thisVal)
{
return EnumValue(_fill);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
return JS_NewInt32(ctx, EnumValue(data->_fill));
}
void fill_set(uint8_t value)
static JSValue fill_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
_fill = static_cast<Drawing::PaletteIndex>(value);
JS_UNPACK_INT32(valueInt, ctx, value)
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
data->_fill = static_cast<Drawing::PaletteIndex>(valueInt);
return JS_UNDEFINED;
}
uint8_t stroke_get() const
static JSValue stroke_get(JSContext* ctx, JSValue thisVal)
{
return EnumValue(_stroke);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
return JS_NewInt32(ctx, EnumValue(data->_stroke));
}
void stroke_set(uint8_t value)
static JSValue stroke_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
_stroke = static_cast<Drawing::PaletteIndex>(value);
JS_UNPACK_INT32(valueInt, ctx, value);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
data->_stroke = static_cast<Drawing::PaletteIndex>(valueInt);
return JS_UNDEFINED;
}
int32_t width_get() const
static JSValue width_get(JSContext* ctx, JSValue thisVal)
{
return _rt.width;
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
return JS_NewInt32(ctx, data->_rt.width);
}
int32_t height_get() const
static JSValue height_get(JSContext* ctx, JSValue thisVal)
{
return _rt.height;
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
return JS_NewInt32(ctx, data->_rt.height);
}
DukValue getImage(uint32_t id)
static JSValue getImage(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
return DukGetImageInfo(_ctx, id);
JS_UNPACK_UINT32(id, ctx, argv[0]);
return JSGetImageInfo(ctx, id);
}
DukValue measureText(const std::string& text)
static JSValue measureText(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_STR(text, ctx, argv[0])
auto width = Drawing::getStringWidth(text, FontStyle::medium);
auto height = Drawing::getStringHeightRaw(text.c_str(), FontStyle::medium);
return ToDuk<ScreenSize>(_ctx, { width, height });
return ToJSValue(ctx, ScreenSize{ width, height });
}
void box(int32_t x, int32_t y, int32_t width, int32_t height)
static JSValue box(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_INT32(x, ctx, argv[0]);
JS_UNPACK_INT32(y, ctx, argv[1]);
JS_UNPACK_INT32(width, ctx, argv[2]);
JS_UNPACK_INT32(height, ctx, argv[3]);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
Drawing::Rectangle::fillInset(
_rt, { x, y, x + width - 1, y + height - 1 }, { static_cast<Drawing::Colour>(_colour.value_or(0)) });
data->_rt, { x, y, x + width - 1, y + height - 1 }, { static_cast<Drawing::Colour>(data->_colour.value_or(0)) });
return JS_UNDEFINED;
}
void well(int32_t x, int32_t y, int32_t width, int32_t height)
static JSValue well(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_INT32(x, ctx, argv[0]);
JS_UNPACK_INT32(y, ctx, argv[1]);
JS_UNPACK_INT32(width, ctx, argv[2]);
JS_UNPACK_INT32(height, ctx, argv[3]);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
Drawing::Rectangle::fillInset(
_rt, { x, y, x + width - 1, y + height - 1 }, { static_cast<Drawing::Colour>(_colour.value_or(0)) },
data->_rt, { x, y, x + width - 1, y + height - 1 }, { static_cast<Drawing::Colour>(data->_colour.value_or(0)) },
Drawing::Rectangle::BorderStyle::inset, Drawing::Rectangle::FillBrightness::light,
Drawing::Rectangle::FillMode::dontLightenWhenInset);
return JS_UNDEFINED;
}
void clear()
static JSValue clear(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
GfxClear(_rt, _fill);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
GfxClear(data->_rt, data->_fill);
return JS_UNDEFINED;
}
void clip(int32_t x, int32_t y, int32_t width, int32_t height)
static JSValue clip(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_INT32(x, ctx, argv[0]);
JS_UNPACK_INT32(y, ctx, argv[1]);
JS_UNPACK_INT32(width, ctx, argv[2]);
JS_UNPACK_INT32(height, ctx, argv[3]);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
Drawing::RenderTarget newRT;
ClipRenderTarget(newRT, _rt, { x, y }, width, height);
_rt = newRT;
ClipRenderTarget(newRT, data->_rt, { x, y }, width, height);
data->_rt = newRT;
return JS_UNDEFINED;
}
void image(uint32_t id, int32_t x, int32_t y)
static JSValue image(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_UINT32(id, ctx, argv[0]);
JS_UNPACK_INT32(x, ctx, argv[1]);
JS_UNPACK_INT32(y, ctx, argv[2]);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
ImageId img;
img = img.WithIndex(id);
if (_paletteId)
if (data->_paletteId)
{
img = img.WithRemap(*_paletteId);
img = img.WithRemap(*data->_paletteId);
}
else
{
if (_colour)
if (data->_colour)
{
img = img.WithPrimary(static_cast<Drawing::Colour>(*_colour));
img = img.WithPrimary(static_cast<Drawing::Colour>(*data->_colour));
}
if (_secondaryColour)
if (data->_secondaryColour)
{
img = img.WithSecondary(static_cast<Drawing::Colour>(*_secondaryColour));
img = img.WithSecondary(static_cast<Drawing::Colour>(*data->_secondaryColour));
}
}
GfxDrawSprite(_rt, img.WithTertiary(static_cast<Drawing::Colour>(_tertiaryColour.value_or(0))), { x, y });
GfxDrawSprite(
data->_rt, img.WithTertiary(static_cast<Drawing::Colour>(data->_tertiaryColour.value_or(0))), { x, y });
return JS_UNDEFINED;
}
void line(int32_t x1, int32_t y1, int32_t x2, int32_t y2)
static void line(GraphicsData* data, int32_t x1, int32_t y1, int32_t x2, int32_t y2)
{
GfxDrawLine(_rt, { { x1, y1 }, { x2, y2 } }, _stroke);
GfxDrawLine(data->_rt, { { x1, y1 }, { x2, y2 } }, data->_stroke);
}
void rect(int32_t x, int32_t y, int32_t width, int32_t height)
static JSValue lineJS(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
if (_stroke != Drawing::PaletteIndex::transparent)
JS_UNPACK_INT32(x1, ctx, argv[0]);
JS_UNPACK_INT32(y1, ctx, argv[1]);
JS_UNPACK_INT32(x2, ctx, argv[2]);
JS_UNPACK_INT32(y2, ctx, argv[3]);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
line(data, x1, y1, x2, y2);
return JS_UNDEFINED;
}
static JSValue rect(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_INT32(x, ctx, argv[0]);
JS_UNPACK_INT32(y, ctx, argv[1]);
JS_UNPACK_INT32(width, ctx, argv[2]);
JS_UNPACK_INT32(height, ctx, argv[3]);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
if (data->_stroke != Drawing::PaletteIndex::transparent)
{
line(x, y, x + width, y);
line(x + width - 1, y + 1, x + width - 1, y + height - 1);
line(x, y + height - 1, x + width, y + height - 1);
line(x, y + 1, x, y + height - 1);
line(data, x, y, x + width, y);
line(data, x + width - 1, y + 1, x + width - 1, y + height - 1);
line(data, x, y + height - 1, x + width, y + height - 1);
line(data, x, y + 1, x, y + height - 1);
x++;
y++;
width -= 2;
height -= 2;
}
if (_fill != Drawing::PaletteIndex::transparent)
if (data->_fill != Drawing::PaletteIndex::transparent)
{
Drawing::Rectangle::fill(_rt, { x, y, x + width - 1, y + height - 1 }, _fill);
Drawing::Rectangle::fill(data->_rt, { x, y, x + width - 1, y + height - 1 }, data->_fill);
}
return JS_UNDEFINED;
}
void text(const std::string& text, int32_t x, int32_t y)
static JSValue text(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
drawText(_rt, { x, y }, text, { static_cast<Drawing::Colour>(_colour.value_or(0)) });
JS_UNPACK_STR(text, ctx, argv[0]);
JS_UNPACK_INT32(x, ctx, argv[1]);
JS_UNPACK_INT32(y, ctx, argv[2]);
GraphicsData* data = gScGraphicsContext.GetOpaque<GraphicsData*>(thisVal);
drawText(data->_rt, { x, y }, text, { static_cast<Drawing::Colour>(data->_colour.value_or(0)) });
return JS_UNDEFINED;
}
};
} // namespace OpenRCT2::Scripting
+107 -69
View File
@@ -16,147 +16,185 @@
#include <openrct2/Context.h>
#include <openrct2/SpriteIds.h>
#include <openrct2/drawing/Image.h>
#include <openrct2/scripting/Duktape.hpp>
namespace OpenRCT2::Scripting
{
class ScImageManager
class ScImageManager;
extern ScImageManager gScImageManager;
class ScImageManager final : public ScBase
{
private:
duk_context* _ctx{};
public:
ScImageManager(duk_context* ctx)
: _ctx(ctx)
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "ImageManager");
}
static void Register(duk_context* ctx)
JSValue New(JSContext* ctx)
{
dukglue_register_method(ctx, &ScImageManager::getPredefinedRange, "getPredefinedRange");
dukglue_register_method(ctx, &ScImageManager::getAvailableAllocationRanges, "getAvailableAllocationRanges");
dukglue_register_method(ctx, &ScImageManager::allocate, "allocate");
dukglue_register_method(ctx, &ScImageManager::free, "free");
dukglue_register_method(ctx, &ScImageManager::getImageInfo, "getImageInfo");
dukglue_register_method(ctx, &ScImageManager::getPixelData, "getPixelData");
dukglue_register_method(ctx, &ScImageManager::setPixelData, "setPixelData");
dukglue_register_method(ctx, &ScImageManager::draw, "draw");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CFUNC_DEF("getPredefinedRange", 1, ScImageManager::getPredefinedRange),
JS_CFUNC_DEF("getAvailableAllocationRanges", 0, ScImageManager::getAvailableAllocationRanges),
JS_CFUNC_DEF("allocate", 1, ScImageManager::allocate),
JS_CFUNC_DEF("free", 1, ScImageManager::free),
JS_CFUNC_DEF("getImageInfo", 1, ScImageManager::getImageInfo),
JS_CFUNC_DEF("getPixelData", 1, ScImageManager::getPixelData),
JS_CFUNC_DEF("setPixelData", 2, ScImageManager::setPixelData),
JS_CFUNC_DEF("draw", 3, ScImageManager::draw)
};
return MakeWithOpaque(ctx, funcs, nullptr);
}
private:
DukValue getPredefinedRange(const std::string& name) const
static JSValue getPredefinedRange(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_STR(name, ctx, argv[0]);
if (name == "g1")
{
return CreateImageIndexRange(0, SPR_G1_END);
return CreateImageIndexRange(ctx, 0, SPR_G1_END);
}
else if (name == "g2")
{
return CreateImageIndexRange(SPR_G2_BEGIN, SPR_G2_END - SPR_G2_BEGIN);
return CreateImageIndexRange(ctx, SPR_G2_BEGIN, SPR_G2_END - SPR_G2_BEGIN);
}
else if (name == "fonts")
{
return CreateImageIndexRange(SPR_FONTS_BEGIN, SPR_FONTS_END - SPR_FONTS_BEGIN);
return CreateImageIndexRange(ctx, SPR_FONTS_BEGIN, SPR_FONTS_END - SPR_FONTS_BEGIN);
}
else if (name == "tracks")
{
return CreateImageIndexRange(SPR_TRACKS_BEGIN, SPR_TRACKS_END - SPR_TRACKS_BEGIN);
return CreateImageIndexRange(ctx, SPR_TRACKS_BEGIN, SPR_TRACKS_END - SPR_TRACKS_BEGIN);
}
else if (name == "csg")
{
return CreateImageIndexRange(SPR_CSG_BEGIN, SPR_CSG_END - SPR_CSG_BEGIN);
return CreateImageIndexRange(ctx, SPR_CSG_BEGIN, SPR_CSG_END - SPR_CSG_BEGIN);
}
else if (name == "allocated")
{
return CreateImageIndexRange(SPR_IMAGE_LIST_BEGIN, SPR_IMAGE_LIST_END - SPR_IMAGE_LIST_BEGIN);
return CreateImageIndexRange(ctx, SPR_IMAGE_LIST_BEGIN, SPR_IMAGE_LIST_END - SPR_IMAGE_LIST_BEGIN);
}
else
{
return ToDuk(_ctx, nullptr);
return JS_NULL;
}
}
DukValue getAvailableAllocationRanges() const
static JSValue getAvailableAllocationRanges(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto ranges = GetAvailableAllocationRanges();
duk_push_array(_ctx);
duk_uarridx_t index = 0;
JSValue arr = JS_NewArray(ctx);
int64_t index = 0;
for (const auto& range : ranges)
{
auto value = CreateImageIndexRange(range.BaseId, range.Count);
value.push();
duk_put_prop_index(_ctx, /* duk stack index */ -2, index);
JS_SetPropertyInt64(ctx, arr, index, CreateImageIndexRange(ctx, range.BaseId, range.Count));
index++;
}
return DukValue::take_from_stack(_ctx);
return arr;
}
DukValue allocate(int32_t count)
static JSValue allocate(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_INT32(count, ctx, argv[0])
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
auto range = AllocateCustomImages(plugin, count);
return range ? CreateImageIndexRange(range->BaseId, range->Count) : ToDuk(_ctx, undefined);
return range ? CreateImageIndexRange(ctx, range->BaseId, range->Count) : JS_UNDEFINED;
}
void free(const DukValue& dukRange)
static JSValue free(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto start = dukRange["start"].as_uint();
auto count = dukRange["count"].as_uint();
auto start = JSToOptionalInt64(ctx, argv[0], "start");
auto count = JSToOptionalInt64(ctx, argv[0], "count");
if (!start.has_value() || !count.has_value())
{
JS_ThrowPlainError(ctx, "Invalid argument");
return JS_EXCEPTION;
}
ImageList range(start, count);
ImageList range(start.value(), count.value());
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
if (!FreeCustomImages(plugin, range))
{
duk_error(_ctx, DUK_ERR_ERROR, "This plugin did not allocate the specified image range.");
}
}
DukValue getImageInfo(int32_t id)
{
return DukGetImageInfo(_ctx, id);
}
DukValue getPixelData(int32_t id)
{
return DukGetImagePixelData(_ctx, id);
}
void setPixelData(int32_t id, const DukValue& pixelData)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
if (!DoesPluginOwnImage(plugin, id))
{
duk_error(_ctx, DUK_ERR_ERROR, "This plugin did not allocate the specified image.");
JS_ThrowPlainError(ctx, "This plugin did not allocate the specified image range.");
return JS_EXCEPTION;
}
DukSetPixelData(_ctx, id, pixelData);
return JS_UNDEFINED;
}
void draw(int32_t id, const DukValue& dukSize, const DukValue& callback)
static JSValue getImageInfo(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto width = dukSize["width"].as_int();
auto height = dukSize["height"].as_int();
JS_UNPACK_INT32(id, ctx, argv[0]);
return JSGetImageInfo(ctx, id);
}
static JSValue getPixelData(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_INT32(id, ctx, argv[0]);
return JSGetImagePixelData(ctx, id);
}
static JSValue setPixelData(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_INT32(id, ctx, argv[0]);
JSValue pixelData = argv[1];
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
if (!DoesPluginOwnImage(plugin, id))
{
duk_error(_ctx, DUK_ERR_ERROR, "This plugin did not allocate the specified image.");
JS_ThrowPlainError(ctx, "This plugin did not allocate the specified image range.");
return JS_EXCEPTION;
}
DukDrawCustomImage(scriptEngine, id, { width, height }, callback);
try
{
JSSetPixelData(ctx, id, pixelData);
}
catch (const std::runtime_error& e)
{
JS_ThrowInternalError(ctx, "%s", e.what());
return JS_EXCEPTION;
}
return JS_UNDEFINED;
}
DukValue CreateImageIndexRange(size_t start, size_t count) const
static JSValue draw(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
DukObject obj(_ctx);
obj.Set("start", static_cast<uint32_t>(start));
obj.Set("count", static_cast<uint32_t>(count));
return obj.Take();
JS_UNPACK_INT32(id, ctx, argv[0])
auto width = JSToOptionalInt64(ctx, argv[1], "width");
auto height = JSToOptionalInt64(ctx, argv[1], "height");
if (!width.has_value() || !height.has_value())
{
JS_ThrowPlainError(ctx, "Invalid size argument");
return JS_EXCEPTION;
}
JSCallback callback(ctx, argv[2]);
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
if (!DoesPluginOwnImage(plugin, id))
{
JS_ThrowPlainError(ctx, "This plugin did not allocate the specified image range.");
return JS_EXCEPTION;
}
JSDrawCustomImage(
scriptEngine, id, { static_cast<int32_t>(width.value()), static_cast<int32_t>(height.value()) }, callback);
return JS_UNDEFINED;
}
static JSValue CreateImageIndexRange(JSContext* ctx, size_t start, size_t count)
{
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "start", JS_NewInt64(ctx, static_cast<uint32_t>(start)));
JS_SetPropertyStr(ctx, obj, "count", JS_NewInt64(ctx, static_cast<uint32_t>(count)));
return obj;
}
};
} // namespace OpenRCT2::Scripting
+62 -66
View File
@@ -11,49 +11,56 @@
#ifdef ENABLE_SCRIPTING
#include <openrct2/scripting/Duktape.hpp>
#include <openrct2/world/MapSelection.h>
namespace OpenRCT2::Scripting
{
class ScTileSelection
{
private:
duk_context* _ctx{};
class ScTileSelection;
extern ScTileSelection gScTileSelection;
class ScTileSelection final : public ScBase
{
public:
ScTileSelection(duk_context* ctx)
: _ctx(ctx)
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "TileSelection");
}
DukValue range_get() const
JSValue New(JSContext* ctx)
{
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("range", ScTileSelection::range_get, ScTileSelection::range_set),
JS_CGETSET_DEF("tiles", ScTileSelection::tiles_get, ScTileSelection::tiles_set),
};
return MakeWithOpaque(ctx, funcs, nullptr);
}
static JSValue range_get(JSContext* ctx, JSValue thisVal)
{
if (gMapSelectFlags.has(MapSelectFlag::enable))
{
DukObject range(_ctx);
JSValue range = JS_NewObject(ctx);
DukObject leftTop(_ctx);
leftTop.Set("x", gMapSelectPositionA.x);
leftTop.Set("y", gMapSelectPositionA.y);
range.Set("leftTop", leftTop.Take());
JSValue leftTop = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, leftTop, "x", JS_NewInt32(ctx, gMapSelectPositionA.x));
JS_SetPropertyStr(ctx, leftTop, "y", JS_NewInt32(ctx, gMapSelectPositionA.y));
JS_SetPropertyStr(ctx, range, "leftTop", leftTop);
DukObject rightBottom(_ctx);
rightBottom.Set("x", gMapSelectPositionB.x);
rightBottom.Set("y", gMapSelectPositionB.y);
range.Set("rightBottom", rightBottom.Take());
return range.Take();
JSValue rightBottom = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, rightBottom, "x", JS_NewInt32(ctx, gMapSelectPositionB.x));
JS_SetPropertyStr(ctx, rightBottom, "y", JS_NewInt32(ctx, gMapSelectPositionB.y));
JS_SetPropertyStr(ctx, range, "rightBottom", rightBottom);
return range;
}
duk_push_null(_ctx);
return DukValue::take_from_stack(_ctx);
return JS_NULL;
}
void range_set(DukValue value)
static JSValue range_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
if (value.type() == DukValue::Type::OBJECT)
if (JS_IsObject(value))
{
auto range = GetMapRange(value);
auto range = GetMapRange(ctx, value);
if (range)
{
gMapSelectPositionA.x = range->GetX1();
@@ -68,48 +75,40 @@ namespace OpenRCT2::Scripting
{
gMapSelectFlags.unset(MapSelectFlag::enable);
}
return JS_UNDEFINED;
}
DukValue tiles_get() const
static JSValue tiles_get(JSContext* ctx, JSValue thisVal)
{
duk_push_array(_ctx);
JSValue tiles = JS_NewArray(ctx);
if (gMapSelectFlags.has(MapSelectFlag::enableConstruct))
{
duk_uarridx_t index = 0;
int64_t index = 0;
for (const auto& tile : MapSelection::getSelectedTiles())
{
duk_push_object(_ctx);
duk_push_int(_ctx, tile.x);
duk_put_prop_string(_ctx, -2, "x");
duk_push_int(_ctx, tile.y);
duk_put_prop_string(_ctx, -2, "y");
duk_put_prop_index(_ctx, -2, index);
JSValue val = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, val, "x", JS_NewInt32(ctx, tile.x));
JS_SetPropertyStr(ctx, val, "y", JS_NewInt32(ctx, tile.y));
JS_SetPropertyInt64(ctx, tiles, index, val);
index++;
}
}
return DukValue::take_from_stack(_ctx);
return tiles;
}
void tiles_set(DukValue value)
static JSValue tiles_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
MapSelection::clearSelectedTiles();
if (value.is_array())
if (JS_IsArray(value))
{
value.push();
auto arrayLen = duk_get_length(_ctx, -1);
for (duk_uarridx_t i = 0; i < arrayLen; i++)
{
if (duk_get_prop_index(_ctx, -1, i))
JSIterateArray(ctx, value, [](JSContext* ctx2, JSValue v) {
auto coords = GetCoordsXY(ctx2, v);
if (coords.has_value())
{
auto dukElement = DukValue::take_from_stack(_ctx);
auto coords = GetCoordsXY(dukElement);
if (coords)
{
MapSelection::addSelectedTile(*coords);
}
MapSelection::addSelectedTile(coords.value());
}
}
duk_pop(_ctx);
});
}
if (MapSelection::getSelectedTiles().empty())
@@ -121,40 +120,37 @@ namespace OpenRCT2::Scripting
{
gMapSelectFlags.set(MapSelectFlag::enableConstruct);
}
}
static void Register(duk_context* ctx)
{
dukglue_register_property(ctx, &ScTileSelection::range_get, &ScTileSelection::range_set, "range");
dukglue_register_property(ctx, &ScTileSelection::tiles_get, &ScTileSelection::tiles_set, "tiles");
return JS_UNDEFINED;
}
private:
static std::optional<CoordsXY> GetCoordsXY(const DukValue& dukCoords)
static std::optional<CoordsXY> GetCoordsXY(JSContext* ctx, JSValue jsCoords)
{
if (dukCoords.type() == DukValue::Type::OBJECT)
if (JS_IsObject(jsCoords))
{
auto dukX = dukCoords["x"];
if (dukX.type() == DukValue::Type::NUMBER)
auto x = JSToOptionalInt(ctx, jsCoords, "x");
auto y = JSToOptionalInt(ctx, jsCoords, "y");
if (x.has_value() && y.has_value())
{
auto dukY = dukCoords["y"];
if (dukY.type() == DukValue::Type::NUMBER)
{
return CoordsXY(dukX.as_int(), dukY.as_int());
}
return CoordsXY(x.value(), y.value());
}
}
return std::nullopt;
}
static std::optional<MapRange> GetMapRange(const DukValue& dukMapRange)
static std::optional<MapRange> GetMapRange(JSContext* ctx, JSValue jsMapRange)
{
if (dukMapRange.type() == DukValue::Type::OBJECT)
if (JS_IsObject(jsMapRange))
{
auto leftTop = GetCoordsXY(dukMapRange["leftTop"]);
JSValue jsLeftTop = JS_GetPropertyStr(ctx, jsMapRange, "leftTop");
auto leftTop = GetCoordsXY(ctx, jsLeftTop);
JS_FreeValue(ctx, jsLeftTop);
if (leftTop.has_value())
{
auto rightBottom = GetCoordsXY(dukMapRange["rightBottom"]);
JSValue jsRightBottom = JS_GetPropertyStr(ctx, jsMapRange, "rightBottom");
auto rightBottom = GetCoordsXY(ctx, jsRightBottom);
JS_FreeValue(ctx, jsRightBottom);
if (rightBottom.has_value())
{
return MapRange(leftTop->x, leftTop->y, rightBottom->x, rightBottom->y);
+332 -202
View File
@@ -17,8 +17,8 @@
#include <openrct2/GameState.h>
#include <openrct2/OpenRCT2.h>
#include <openrct2/ParkImporter.h>
#include <openrct2/core/EnumMap.hpp>
#include <openrct2/core/String.hpp>
#include <openrct2/entity/EntityRegistry.h>
#include <openrct2/object/ObjectManager.h>
#include <openrct2/scenario/Scenario.h>
#include <openrct2/scenes/title/TitleScene.h>
@@ -48,7 +48,7 @@ namespace OpenRCT2::Scripting
LoadSc,
};
static const DukEnumMap<TitleScript> TitleScriptMap(
static const EnumMap<TitleScript> TitleScriptMap(
{
{ Title::LoadParkCommand::ScriptingName, TitleScript::Load },
{ Title::SetLocationCommand::ScriptingName, TitleScript::Location },
@@ -62,193 +62,224 @@ namespace OpenRCT2::Scripting
{ Title::EndCommand::ScriptingName, TitleScript::End },
});
template<>
DukValue ToDuk(duk_context* ctx, const TitleScript& value)
inline JSValue TitleScriptToJS(JSContext* ctx, TitleScript value)
{
return ToDuk(ctx, TitleScriptMap[value]);
return JSFromStdString(ctx, TitleScriptMap[value]);
}
template<>
DukValue ToDuk(duk_context* ctx, const Title::TitleCommand& value)
inline JSValue TitleCommandToJS(JSContext* ctx, const Title::TitleCommand& value)
{
using namespace OpenRCT2::Title;
DukObject obj(ctx);
JSValue obj = JS_NewObject(ctx);
std::visit(
[&obj](auto&& command) {
[ctx, &obj](auto&& command) {
using T = std::decay_t<decltype(command)>;
obj.Set("type", T::ScriptingName);
JS_SetPropertyStr(ctx, obj, "type", JSFromStdString(ctx, T::ScriptingName));
if constexpr (std::is_same_v<T, LoadParkCommand>)
{
obj.Set("index", command.SaveIndex);
JS_SetPropertyStr(ctx, obj, "index", JS_NewInt32(ctx, command.SaveIndex));
}
else if constexpr (std::is_same_v<T, SetLocationCommand>)
{
obj.Set("x", command.Location.X);
obj.Set("y", command.Location.Y);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, command.Location.X));
JS_SetPropertyStr(ctx, obj, "y", JS_NewInt32(ctx, command.Location.Y));
}
else if constexpr (std::is_same_v<T, RotateViewCommand>)
{
obj.Set("rotations", command.Rotations);
JS_SetPropertyStr(ctx, obj, "rotations", JS_NewInt32(ctx, command.Rotations));
}
else if constexpr (std::is_same_v<T, SetZoomCommand>)
{
obj.Set("zoom", command.Zoom);
JS_SetPropertyStr(ctx, obj, "zoom", JS_NewInt32(ctx, command.Zoom));
}
else if constexpr (std::is_same_v<T, FollowEntityCommand>)
{
if (command.Follow.SpriteIndex.IsNull())
obj.Set("id", nullptr);
JS_SetPropertyStr(ctx, obj, "id", JS_NULL);
else
obj.Set("id", command.Follow.SpriteIndex.ToUnderlying());
JS_SetPropertyStr(ctx, obj, "id", JS_NewInt32(ctx, command.Follow.SpriteIndex.ToUnderlying()));
}
else if constexpr (std::is_same_v<T, SetSpeedCommand>)
{
obj.Set("speed", command.Speed);
JS_SetPropertyStr(ctx, obj, "speed", JS_NewInt32(ctx, command.Speed));
}
else if constexpr (std::is_same_v<T, WaitCommand>)
{
obj.Set("duration", command.Milliseconds);
JS_SetPropertyStr(ctx, obj, "duration", JS_NewInt32(ctx, command.Milliseconds));
}
else if constexpr (std::is_same_v<T, LoadScenarioCommand>)
{
obj.Set("scenario", String::toStringView(command.Scenario, sizeof(command.Scenario)));
JS_SetPropertyStr(ctx, obj, "scenario", JSFromStdString(ctx, command.Scenario));
}
},
value);
return obj.Take();
return obj;
}
template<>
TitleScript FromDuk(const DukValue& value)
{
if (value.type() == DukValue::Type::STRING)
return TitleScriptMap[value.as_string()];
throw DukException() << "Invalid title command id";
}
template<>
Title::TitleCommand FromDuk(const DukValue& value)
inline std::optional<Title::TitleCommand> TitleCommandFromJS(JSContext* ctx, JSValue value)
{
using namespace OpenRCT2::Title;
auto type = FromDuk<TitleScript>(value["type"]);
TitleCommand command{};
switch (type)
auto type = TitleScriptMap.TryGet(JSToStdString(ctx, value, "type"));
if (!type.has_value())
return std::nullopt;
switch (type.value())
{
case TitleScript::Load:
command = LoadParkCommand{ static_cast<uint8_t>(value["index"].as_uint()) };
break;
case TitleScript::Location:
command = SetLocationCommand{
static_cast<uint8_t>(value["x"].as_uint()),
static_cast<uint8_t>(value["y"].as_uint()),
};
break;
case TitleScript::Rotate:
command = RotateViewCommand{ static_cast<uint8_t>(value["rotations"].as_uint()) };
break;
case TitleScript::Zoom:
command = SetZoomCommand{ static_cast<uint8_t>(value["zoom"].as_uint()) };
break;
case TitleScript::Follow:
{
auto dukId = value["id"];
if (dukId.type() == DukValue::Type::NUMBER)
auto index = JSToOptionalInt(ctx, value, "index");
if (!index.has_value())
return std::nullopt;
return LoadParkCommand{ static_cast<uint8_t>(index.value()) };
}
case TitleScript::Location:
{
auto x = JSToOptionalInt(ctx, value, "x");
auto y = JSToOptionalInt(ctx, value, "y");
if (!x.has_value() || !y.has_value())
return std::nullopt;
return SetLocationCommand{
static_cast<uint8_t>(x.value()),
static_cast<uint8_t>(y.value()),
};
}
case TitleScript::Rotate:
{
auto rotations = JSToOptionalInt(ctx, value, "rotations");
if (!rotations.has_value())
return std::nullopt;
return RotateViewCommand{ static_cast<uint8_t>(rotations.value()) };
}
case TitleScript::Zoom:
{
auto zoom = JSToOptionalInt(ctx, value, "zoom");
if (!zoom.has_value())
return std::nullopt;
return SetZoomCommand{ static_cast<uint8_t>(zoom.value()) };
}
case TitleScript::Follow:
if (auto id = JSToOptionalInt(ctx, value, "id"); id.has_value())
{
command = FollowEntityCommand{ EntityId::FromUnderlying(dukId.as_uint()) };
return FollowEntityCommand{ EntityId::FromUnderlying(id.value()) };
}
else
{
command = FollowEntityCommand{ EntityId::GetNull() };
return FollowEntityCommand{ EntityId::GetNull() };
}
break;
}
case TitleScript::Speed:
command = SetSpeedCommand{ static_cast<uint8_t>(value["speed"].as_uint()) };
break;
{
auto speed = JSToOptionalInt(ctx, value, "speed");
if (!speed.has_value())
return std::nullopt;
return SetSpeedCommand{ static_cast<uint8_t>(speed.value()) };
}
case TitleScript::Wait:
command = WaitCommand{ static_cast<uint16_t>(value["duration"].as_uint()) };
break;
{
auto duration = JSToOptionalInt(ctx, value, "duration");
if (!duration.has_value())
return std::nullopt;
return WaitCommand{ static_cast<uint16_t>(duration.value()) };
}
case TitleScript::LoadSc:
{
auto scenario = JSToOptionalStdString(ctx, value, "scenario");
if (!scenario.has_value())
return std::nullopt;
auto loadScenarioCommand = LoadScenarioCommand{};
String::set(
loadScenarioCommand.Scenario, sizeof(loadScenarioCommand.Scenario), value["scenario"].as_c_string());
command = loadScenarioCommand;
break;
String::set(loadScenarioCommand.Scenario, sizeof(loadScenarioCommand.Scenario), scenario.value().c_str());
return loadScenarioCommand;
}
case TitleScript::Restart:
command = RestartCommand{};
break;
return RestartCommand{};
case TitleScript::End:
command = EndCommand{};
break;
return EndCommand{};
default:
break;
return std::nullopt;
}
return command;
}
class ScTitleSequencePark
class ScTitleSequencePark;
extern ScTitleSequencePark gScTitleSequencePark;
class ScTitleSequencePark : public ScBase
{
private:
std::string _titleSequencePath;
std::string _fileName;
public:
ScTitleSequencePark(std::string_view path, std::string_view fileName)
: _titleSequencePath(path)
, _fileName(fileName)
struct OpaqueData
{
std::string _titleSequencePath{};
std::string _fileName{};
};
static JSValue fileName_get(JSContext* ctx, JSValue thisVal)
{
OpaqueData* data = gScTitleSequencePark.GetOpaque<OpaqueData*>(thisVal);
if (!data)
return JS_UNDEFINED;
return JSFromStdString(ctx, data->_fileName);
}
private:
std::string fileName_get() const
static JSValue fileName_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
return _fileName;
}
JS_UNPACK_STR(valueStr, ctx, value);
OpaqueData* data = gScTitleSequencePark.GetOpaque<OpaqueData*>(thisVal);
if (!data)
return JS_UNDEFINED;
void fileName_set(const std::string& value)
{
if (value == _fileName)
return;
if (valueStr == data->_fileName)
return JS_UNDEFINED;
auto seq = Title::LoadTitleSequence(_titleSequencePath);
auto seq = Title::LoadTitleSequence(data->_titleSequencePath);
if (seq != nullptr)
{
// Check if name already in use
auto index = GetIndex(*seq, value);
auto index = GetIndex(*seq, valueStr);
if (!index)
{
index = GetIndex(*seq, _fileName);
index = GetIndex(*seq, data->_fileName);
if (index)
{
TitleSequenceRenamePark(*seq, *index, value.c_str());
TitleSequenceRenamePark(*seq, *index, valueStr.c_str());
TitleSequenceSave(*seq);
}
}
}
return JS_UNDEFINED;
}
void delete_()
static JSValue delete_(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto seq = Title::LoadTitleSequence(_titleSequencePath);
OpaqueData* data = gScTitleSequencePark.GetOpaque<OpaqueData*>(thisVal);
if (!data)
return JS_UNDEFINED;
auto seq = Title::LoadTitleSequence(data->_titleSequencePath);
if (seq != nullptr)
{
auto index = GetIndex(*seq, _fileName);
auto index = GetIndex(*seq, data->_fileName);
if (index)
{
OpenRCT2::Title::TitleSequenceRemovePark(*seq, *index);
OpenRCT2::Title::TitleSequenceSave(*seq);
}
}
return JS_UNDEFINED;
}
void load()
static JSValue load(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto seq = Title::LoadTitleSequence(_titleSequencePath);
OpaqueData* data = gScTitleSequencePark.GetOpaque<OpaqueData*>(thisVal);
if (!data)
return JS_UNDEFINED;
auto seq = Title::LoadTitleSequence(data->_titleSequencePath);
if (seq != nullptr)
{
auto index = GetIndex(*seq, _fileName);
auto index = GetIndex(*seq, data->_fileName);
if (index)
{
auto handle = OpenRCT2::Title::TitleSequenceGetParkHandle(*seq, *index);
@@ -281,22 +312,39 @@ namespace OpenRCT2::Scripting
}
catch (const std::exception&)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
duk_error(ctx, DUK_ERR_ERROR, "Unable to load park.");
JS_ThrowPlainError(ctx, "Unable to load park.");
return JS_EXCEPTION;
}
}
}
return JS_UNDEFINED;
}
public:
static void Register(duk_context* ctx)
JSValue New(JSContext* ctx, std::string_view path, std::string_view fileName)
{
dukglue_register_property(ctx, &ScTitleSequencePark::fileName_get, &ScTitleSequencePark::fileName_set, "fileName");
dukglue_register_method(ctx, &ScTitleSequencePark::delete_, "delete");
dukglue_register_method(ctx, &ScTitleSequencePark::load, "load");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("fileName", ScTitleSequencePark::fileName_get, ScTitleSequencePark::fileName_set),
JS_CFUNC_DEF("delete", 0, ScTitleSequencePark::delete_),
JS_CFUNC_DEF("load", 0, ScTitleSequencePark::load),
};
return MakeWithOpaque(ctx, funcs, new OpaqueData{ std::string(path), std::string(fileName) });
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "TitleSequencePark", Finalize);
}
private:
static void Finalize(JSRuntime* rt, JSValue thisVal)
{
OpaqueData* data = gScTitleSequencePark.GetOpaque<OpaqueData*>(thisVal);
if (data)
delete data;
}
static std::optional<size_t> GetIndex(const Title::TitleSequence& seq, const std::string_view needle)
{
for (size_t i = 0; i < seq.Saves.size(); i++)
@@ -310,189 +358,236 @@ namespace OpenRCT2::Scripting
}
};
class ScTitleSequence
class ScTitleSequence;
extern ScTitleSequence gScTitleSequence;
class ScTitleSequence : public ScBase
{
private:
std::string _path;
public:
ScTitleSequence(const std::string& path)
struct OpaqueData
{
_path = path;
}
std::string _path;
};
private:
std::string name_get() const
static JSValue name_get(JSContext* ctx, JSValue thisVal)
{
const auto* item = GetItem();
const auto* item = GetItem(ctx, thisVal);
if (item != nullptr)
{
return item->Name;
return JSFromStdString(ctx, item->Name);
}
return {};
return JSFromStdString(ctx, {});
}
void name_set(const std::string& value)
static JSValue name_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
auto index = GetManagerIndex();
JS_UNPACK_STR(valueStr, ctx, value)
OpaqueData* data = gScTitleSequence.GetOpaque<OpaqueData*>(thisVal);
if (!data)
return JS_UNDEFINED;
auto index = GetManagerIndex(ctx, thisVal);
if (index)
{
auto newIndex = TitleSequenceManager::RenameItem(*index, value.c_str());
auto newIndex = TitleSequenceManager::RenameItem(*index, valueStr.c_str());
// Update path to new value
auto newItem = TitleSequenceManager::GetItem(newIndex);
_path = newItem != nullptr ? newItem->Path : std::string();
data->_path = newItem != nullptr ? newItem->Path : std::string();
}
return JS_UNDEFINED;
}
std::string path_get() const
static JSValue path_get(JSContext* ctx, JSValue thisVal)
{
const auto* item = GetItem();
const auto* item = GetItem(ctx, thisVal);
if (item != nullptr)
{
return item->Path;
return JSFromStdString(ctx, item->Path);
}
return {};
return JSFromStdString(ctx, {});
}
bool isDirectory_get() const
static JSValue isDirectory_get(JSContext* ctx, JSValue thisVal)
{
const auto* item = GetItem();
const auto* item = GetItem(ctx, thisVal);
if (item != nullptr)
{
return !item->IsZip;
return JS_NewBool(ctx, !item->IsZip);
}
return {};
return JS_NewBool(ctx, {});
}
bool isReadOnly_get() const
static JSValue isReadOnly_get(JSContext* ctx, JSValue thisVal)
{
const auto* item = GetItem();
const auto* item = GetItem(ctx, thisVal);
if (item != nullptr)
{
return item->PredefinedIndex != TitleSequenceManager::kPredefinedIndexCustom;
return JS_NewBool(ctx, item->PredefinedIndex != TitleSequenceManager::kPredefinedIndexCustom);
}
return {};
return JS_NewBool(ctx, {});
}
std::vector<std::shared_ptr<ScTitleSequencePark>> parks_get() const
static JSValue parks_get(JSContext* ctx, JSValue thisVal)
{
std::vector<std::shared_ptr<ScTitleSequencePark>> result;
auto titleSeq = Title::LoadTitleSequence(_path);
JSValue result = JS_NewArray(ctx);
OpaqueData* data = gScTitleSequence.GetOpaque<OpaqueData*>(thisVal);
if (!data)
return result;
auto titleSeq = Title::LoadTitleSequence(data->_path);
if (titleSeq != nullptr)
{
for (size_t i = 0; i < titleSeq->Saves.size(); i++)
{
result.push_back(std::make_shared<ScTitleSequencePark>(_path, titleSeq->Saves[i]));
JS_SetPropertyInt64(ctx, result, i, gScTitleSequencePark.New(ctx, data->_path, titleSeq->Saves[i]));
}
}
return result;
}
std::vector<DukValue> commands_get() const
static JSValue commands_get(JSContext* ctx, JSValue thisVal)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
JSValue result = JS_NewArray(ctx);
OpaqueData* data = gScTitleSequence.GetOpaque<OpaqueData*>(thisVal);
if (!data)
return result;
std::vector<DukValue> result;
auto titleSeq = Title::LoadTitleSequence(_path);
auto titleSeq = Title::LoadTitleSequence(data->_path);
if (titleSeq != nullptr)
{
int64_t idx = 0;
for (const auto& command : titleSeq->Commands)
{
result.push_back(ToDuk(ctx, command));
JS_SetPropertyInt64(ctx, result, idx++, TitleCommandToJS(ctx, command));
}
}
return result;
}
void commands_set(const std::vector<DukValue>& value)
static JSValue commands_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_ARRAY(array, ctx, value);
OpaqueData* data = gScTitleSequence.GetOpaque<OpaqueData*>(thisVal);
if (!data)
return JS_UNDEFINED;
int64_t length;
if (JS_GetLength(ctx, array, &length) != 0)
return JS_UNDEFINED;
std::vector<Title::TitleCommand> commands;
for (const auto& v : value)
commands.reserve(length);
for (int64_t i = 0; i < length; i++)
{
auto command = FromDuk<Title::TitleCommand>(v);
commands.push_back(std::move(command));
JSValue element = JS_GetPropertyInt64(ctx, array, i);
auto command = TitleCommandFromJS(ctx, element);
JS_FreeValue(ctx, element);
if (!command.has_value())
{
JS_ThrowPlainError(ctx, "Invalid command");
return JS_EXCEPTION;
}
commands.push_back(command.value());
}
auto titleSeq = Title::LoadTitleSequence(_path);
auto titleSeq = Title::LoadTitleSequence(data->_path);
titleSeq->Commands = commands;
OpenRCT2::Title::TitleSequenceSave(*titleSeq);
return JS_UNDEFINED;
}
void addPark(const std::string& path, const std::string& fileName)
static JSValue addPark(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto titleSeq = Title::LoadTitleSequence(_path);
JS_UNPACK_STR(path, ctx, argv[0]);
JS_UNPACK_STR(fileName, ctx, argv[1]);
OpaqueData* data = gScTitleSequence.GetOpaque<OpaqueData*>(thisVal);
if (!data)
return JS_UNDEFINED;
auto titleSeq = Title::LoadTitleSequence(data->_path);
OpenRCT2::Title::TitleSequenceAddPark(*titleSeq, path.c_str(), fileName.c_str());
OpenRCT2::Title::TitleSequenceSave(*titleSeq);
return JS_UNDEFINED;
}
std::shared_ptr<ScTitleSequence> clone(const std::string& name) const
static JSValue clone(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto copyIndex = GetManagerIndex();
JS_UNPACK_STR(name, ctx, argv[0]);
auto copyIndex = GetManagerIndex(ctx, thisVal);
if (copyIndex)
{
auto index = TitleSequenceManager::DuplicateItem(*copyIndex, name.c_str());
auto* item = TitleSequenceManager::GetItem(index);
if (item != nullptr)
{
return std::make_shared<ScTitleSequence>(item->Path);
return gScTitleSequence.New(ctx, item->Path);
}
}
return nullptr;
return JS_NULL;
}
void delete_()
static JSValue delete_(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto index = GetManagerIndex();
OpaqueData* data = gScTitleSequence.GetOpaque<OpaqueData*>(thisVal);
if (!data)
return JS_UNDEFINED;
auto index = GetManagerIndex(ctx, thisVal);
if (index)
{
TitleSequenceManager::DeleteItem(*index);
}
_path = {};
data->_path = {};
return JS_UNDEFINED;
}
bool isPlaying_get() const
static bool IsPlaying(JSContext* ctx, JSValue thisVal)
{
auto index = GetManagerIndex();
auto index = GetManagerIndex(ctx, thisVal);
return index && TitleIsPreviewingSequence() && *index == TitleGetCurrentSequence();
}
DukValue position_get() const
static JSValue isPlaying_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
if (isPlaying_get())
return JS_NewBool(ctx, IsPlaying(ctx, thisVal));
}
static JSValue position_get(JSContext* ctx, JSValue thisVal)
{
if (IsPlaying(ctx, thisVal))
{
auto* player = static_cast<ITitleSequencePlayer*>(TitleGetSequencePlayer());
if (player != nullptr)
{
return ToDuk(ctx, player->GetCurrentPosition());
return JS_NewInt32(ctx, player->GetCurrentPosition());
}
}
return ToDuk(ctx, nullptr);
return JS_NULL;
}
void play()
static JSValue play(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto index = GetManagerIndex();
auto index = GetManagerIndex(ctx, thisVal);
if (index && (!TitleIsPreviewingSequence() || *index != TitleGetCurrentSequence()))
{
if (!TitlePreviewSequence(*index))
{
duk_error(ctx, DUK_ERR_ERROR, "Failed to load title sequence");
JS_ThrowPlainError(ctx, "Failed to load title sequence");
return JS_EXCEPTION;
}
else if (gLegacyScene != LegacyScene::titleSequence)
{
gPreviewingTitleSequenceInGame = true;
}
}
return JS_UNDEFINED;
}
void seek(int32_t position)
static JSValue seek(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
if (isPlaying_get())
JS_UNPACK_INT32(position, ctx, argv[0]);
if (IsPlaying(ctx, thisVal))
{
auto* player = static_cast<ITitleSequencePlayer*>(TitleGetSequencePlayer());
try
@@ -502,47 +597,71 @@ namespace OpenRCT2::Scripting
}
catch (...)
{
duk_error(ctx, DUK_ERR_ERROR, "Failed to seek");
JS_ThrowPlainError(ctx, "Failed to seek");
return JS_EXCEPTION;
}
}
return JS_UNDEFINED;
}
void stop()
static JSValue stop(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
if (isPlaying_get())
if (IsPlaying(ctx, thisVal))
{
TitleStopPreviewingSequence();
}
return JS_UNDEFINED;
}
public:
static void Register(duk_context* ctx)
JSValue New(JSContext* ctx, std::string_view path)
{
dukglue_register_property(ctx, &ScTitleSequence::name_get, &ScTitleSequence::name_set, "name");
dukglue_register_property(ctx, &ScTitleSequence::path_get, nullptr, "path");
dukglue_register_property(ctx, &ScTitleSequence::isDirectory_get, nullptr, "isDirectory");
dukglue_register_property(ctx, &ScTitleSequence::isReadOnly_get, nullptr, "isReadOnly");
dukglue_register_property(ctx, &ScTitleSequence::parks_get, nullptr, "parks");
dukglue_register_property(ctx, &ScTitleSequence::commands_get, &ScTitleSequence::commands_set, "commands");
dukglue_register_property(ctx, &ScTitleSequence::isPlaying_get, nullptr, "isPlaying");
dukglue_register_property(ctx, &ScTitleSequence::position_get, nullptr, "position");
dukglue_register_method(ctx, &ScTitleSequence::addPark, "addPark");
dukglue_register_method(ctx, &ScTitleSequence::clone, "clone");
dukglue_register_method(ctx, &ScTitleSequence::delete_, "delete");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("name", ScTitleSequence::name_get, ScTitleSequence::name_set),
JS_CGETSET_DEF("path", ScTitleSequence::path_get, nullptr),
JS_CGETSET_DEF("isDirectory", ScTitleSequence::isDirectory_get, nullptr),
JS_CGETSET_DEF("isReadOnly", ScTitleSequence::isReadOnly_get, nullptr),
JS_CGETSET_DEF("parks", ScTitleSequence::parks_get, nullptr),
JS_CGETSET_DEF("commands", ScTitleSequence::commands_get, ScTitleSequence::commands_set),
JS_CGETSET_DEF("isPlaying", ScTitleSequence::isPlaying_get, nullptr),
JS_CGETSET_DEF("position", ScTitleSequence::position_get, nullptr),
dukglue_register_method(ctx, &ScTitleSequence::play, "play");
dukglue_register_method(ctx, &ScTitleSequence::seek, "seek");
dukglue_register_method(ctx, &ScTitleSequence::stop, "stop");
JS_CFUNC_DEF("addPark", 2, ScTitleSequence::addPark),
JS_CFUNC_DEF("clone", 1, ScTitleSequence::clone),
JS_CFUNC_DEF("delete", 0, ScTitleSequence::delete_),
JS_CFUNC_DEF("play", 0, ScTitleSequence::play),
JS_CFUNC_DEF("seek", 1, ScTitleSequence::seek),
JS_CFUNC_DEF("stop", 0, ScTitleSequence::stop),
};
return MakeWithOpaque(ctx, funcs, new OpaqueData{ std::string(path) });
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "TitleSequence", Finalize);
}
private:
std::optional<size_t> GetManagerIndex() const
static void Finalize(JSRuntime* rt, JSValue thisVal)
{
OpaqueData* data = gScTitleSequence.GetOpaque<OpaqueData*>(thisVal);
if (data)
delete data;
}
static std::optional<size_t> GetManagerIndex(JSContext* ctx, JSValue thisVal)
{
OpaqueData* data = gScTitleSequence.GetOpaque<OpaqueData*>(thisVal);
if (!data)
return std::nullopt;
auto count = TitleSequenceManager::GetCount();
for (size_t i = 0; i < count; i++)
{
auto item = TitleSequenceManager::GetItem(i);
if (item != nullptr && item->Path == _path)
if (item != nullptr && item->Path == data->_path)
{
return i;
}
@@ -550,9 +669,9 @@ namespace OpenRCT2::Scripting
return std::nullopt;
}
const TitleSequenceManager::Item* GetItem() const
static const TitleSequenceManager::Item* GetItem(JSContext* ctx, JSValue thisVal)
{
auto index = GetManagerIndex();
auto index = GetManagerIndex(ctx, thisVal);
if (index)
{
return TitleSequenceManager::GetItem(*index);
@@ -561,37 +680,48 @@ namespace OpenRCT2::Scripting
}
};
class ScTitleSequenceManager
class ScTitleSequenceManager;
extern ScTitleSequenceManager gScTitleSequenceManager;
class ScTitleSequenceManager : public ScBase
{
private:
std::vector<std::shared_ptr<ScTitleSequence>> titleSequences_get() const
static JSValue titleSequences_get(JSContext* ctx, JSValue thisVal)
{
std::vector<std::shared_ptr<ScTitleSequence>> result;
JSValue result = JS_NewArray(ctx);
auto count = TitleSequenceManager::GetCount();
for (size_t i = 0; i < count; i++)
{
const auto& path = TitleSequenceManager::GetItem(i)->Path;
result.push_back(std::make_shared<ScTitleSequence>(path));
JS_SetPropertyInt64(ctx, result, i, gScTitleSequence.New(ctx, path));
}
return result;
}
std::shared_ptr<ScTitleSequence> create(const std::string& name)
static JSValue create(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_STR(name, ctx, argv[0])
auto index = TitleSequenceManager::CreateItem(name.c_str());
auto* item = TitleSequenceManager::GetItem(index);
if (item != nullptr)
{
return std::make_shared<ScTitleSequence>(item->Path);
return gScTitleSequence.New(ctx, item->Path);
}
return nullptr;
return JS_NULL;
}
public:
static void Register(duk_context* ctx)
JSValue New(JSContext* ctx)
{
dukglue_register_property(ctx, &ScTitleSequenceManager::titleSequences_get, nullptr, "titleSequences");
dukglue_register_method(ctx, &ScTitleSequenceManager::create, "create");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("titleSequences", ScTitleSequenceManager::titleSequences_get, nullptr),
JS_CFUNC_DEF("create", 1, ScTitleSequenceManager::create),
};
return MakeWithOpaque(ctx, funcs, nullptr);
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "TitleSequenceManager");
}
};
} // namespace OpenRCT2::Scripting
+321 -240
View File
@@ -11,12 +11,14 @@
#ifdef ENABLE_SCRIPTING
#include "../interface/Window.h"
#include "../windows/Windows.h"
#include "CustomMenu.h"
#include "CustomWindow.h"
#include "ScImageManager.hpp"
#include "ScTileSelection.hpp"
#include "ScViewport.hpp"
#include "ScWindow.hpp"
#include "ScWindow.h"
#include <algorithm>
#include <memory>
@@ -24,416 +26,495 @@
#include <openrct2/Input.h>
#include <openrct2/scenario/ScenarioCategory.h>
#include <openrct2/scenario/ScenarioRepository.h>
#include <openrct2/scripting/Duktape.hpp>
#include <openrct2/scripting/ScriptEngine.h>
#include <string>
namespace OpenRCT2::Scripting
{
class Plugin;
}
class ScUi;
extern ScUi gScUi;
class ScTool;
extern ScTool gScTool;
namespace OpenRCT2::Ui::Windows
{
WindowBase* WindowCustomOpen(std::shared_ptr<Scripting::Plugin> owner, DukValue dukDesc);
}
static const EnumMap<Scenario::Category> ScenarioCategoryMap{ {
{ "beginner", Scenario::Category::beginner },
{ "challenging", Scenario::Category::challenging },
{ "expert", Scenario::Category::expert },
{ "real", Scenario::Category::real },
{ "other", Scenario::Category::other },
{ "dlc", Scenario::Category::dlc },
{ "build_your_own", Scenario::Category::buildYourOwn },
{ "competitions", Scenario::Category::competitions },
} };
namespace OpenRCT2::Scripting
{
static const DukEnumMap<Scenario::Category> ScenarioCategoryMap(
{
{ "beginner", Scenario::Category::beginner },
{ "challenging", Scenario::Category::challenging },
{ "expert", Scenario::Category::expert },
{ "real", Scenario::Category::real },
{ "other", Scenario::Category::other },
{ "dlc", Scenario::Category::dlc },
{ "build_your_own", Scenario::Category::buildYourOwn },
{ "competitions", Scenario::Category::competitions },
});
static const EnumMap<ScenarioSource> ScenarioSourceMap{
{ "rct1", ScenarioSource::RCT1 }, { "rct1_aa", ScenarioSource::RCT1_AA }, { "rct1_ll", ScenarioSource::RCT1_LL },
{ "rct2", ScenarioSource::RCT2 }, { "rct2_ww", ScenarioSource::RCT2_WW }, { "rct2_tt", ScenarioSource::RCT2_TT },
{ "real", ScenarioSource::Real }, { "extras", ScenarioSource::Extras }, { "other", ScenarioSource::Other },
};
static const DukEnumMap<ScenarioSource> ScenarioSourceMap(
{
{ "rct1", ScenarioSource::RCT1 },
{ "rct1_aa", ScenarioSource::RCT1_AA },
{ "rct1_ll", ScenarioSource::RCT1_LL },
{ "rct2", ScenarioSource::RCT2 },
{ "rct2_ww", ScenarioSource::RCT2_WW },
{ "rct2_tt", ScenarioSource::RCT2_TT },
{ "real", ScenarioSource::Real },
{ "extras", ScenarioSource::Extras },
{ "other", ScenarioSource::Other },
});
template<>
inline DukValue ToDuk(duk_context* ctx, const Scenario::Category& value)
inline JSValue ScenarioCategoryToJS(JSContext* ctx, Scenario::Category value)
{
const auto& entry = ScenarioCategoryMap.find(value);
if (entry != ScenarioCategoryMap.end())
return ToDuk(ctx, entry->first);
return ToDuk(ctx, ScenarioCategoryMap[Scenario::Category::other]);
return JSFromStdString(ctx, entry->first);
return JSFromStdString(ctx, ScenarioCategoryMap[Scenario::Category::other]);
}
template<>
inline DukValue ToDuk(duk_context* ctx, const ScenarioSource& value)
inline JSValue ScenarioSourceToJS(JSContext* ctx, ScenarioSource value)
{
const auto& entry = ScenarioSourceMap.find(value);
if (entry != ScenarioSourceMap.end())
return ToDuk(ctx, entry->first);
return ToDuk(ctx, ScenarioSourceMap[ScenarioSource::Other]);
return JSFromStdString(ctx, entry->first);
return JSFromStdString(ctx, ScenarioSourceMap[ScenarioSource::Other]);
}
class ScTool
class ScTool final : public ScBase
{
private:
duk_context* _ctx{};
public:
ScTool(duk_context* ctx)
: _ctx(ctx)
static JSValue id_get(JSContext* ctx, JSValue thisVal)
{
return JSFromStdString(ctx, ActiveCustomTool ? ActiveCustomTool->Id : "");
}
static void Register(duk_context* ctx)
static JSValue cursor_get(JSContext* ctx, JSValue thisVal)
{
dukglue_register_property(ctx, &ScTool::id_get, nullptr, "id");
dukglue_register_property(ctx, &ScTool::cursor_get, nullptr, "cursor");
dukglue_register_method(ctx, &ScTool::cancel, "cancel");
return CursorIDToJSValue(ctx, static_cast<CursorID>(gCurrentToolId));
}
private:
std::string id_get() const
{
return ActiveCustomTool ? ActiveCustomTool->Id : "";
}
DukValue cursor_get() const
{
return ToDuk(_ctx, static_cast<CursorID>(gCurrentToolId));
}
void cancel()
static JSValue cancel(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
ToolCancel();
return JS_UNDEFINED;
}
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("id", ScTool::id_get, nullptr),
JS_CGETSET_DEF("cursor", ScTool::cursor_get, nullptr),
JS_CFUNC_DEF("cancel", 0, ScTool::cancel),
};
public:
JSValue New(JSContext* ctx)
{
return MakeWithOpaque(ctx, funcs, nullptr);
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Tool");
}
};
class ScUi
class ScUi final : public ScBase
{
private:
ScriptEngine& _scriptEngine;
public:
ScUi(ScriptEngine& scriptEngine)
: _scriptEngine(scriptEngine)
static JSValue width_get(JSContext* ctx, JSValue thisVal)
{
return JS_NewInt32(ctx, ContextGetWidth());
}
static JSValue height_get(JSContext* ctx, JSValue thisVal)
{
return JS_NewInt32(ctx, ContextGetHeight());
}
static JSValue windows_get(JSContext* ctx, JSValue thisVal)
{
return JS_NewInt32(ctx, static_cast<int32_t>(gWindowList.size()));
}
private:
int32_t width_get() const
static JSValue mainViewport_get(JSContext* ctx, JSValue thisVal)
{
return ContextGetWidth();
}
int32_t height_get() const
{
return ContextGetHeight();
}
int32_t windows_get() const
{
return static_cast<int32_t>(gWindowList.size());
return gScViewport.New(ctx, WindowClass::mainWindow);
}
std::shared_ptr<ScViewport> mainViewport_get() const
static JSValue tileSelection_get(JSContext* ctx, JSValue thisVal)
{
return std::make_shared<ScViewport>(WindowClass::mainWindow);
return gScTileSelection.New(ctx);
}
std::shared_ptr<ScTileSelection> tileSelection_get() const
{
return std::make_shared<ScTileSelection>(_scriptEngine.GetContext());
}
std::shared_ptr<ScTool> tool_get() const
static JSValue tool_get(JSContext* ctx, JSValue thisVal)
{
if (gInputFlags.has(InputFlag::toolActive))
{
return std::make_shared<ScTool>(_scriptEngine.GetContext());
return gScTool.New(ctx);
}
return {};
return JS_NULL;
}
std::shared_ptr<ScImageManager> imageManager_get() const
static JSValue imageManager_get(JSContext* ctx, JSValue thisVal)
{
return std::make_shared<ScImageManager>(_scriptEngine.GetContext());
return gScImageManager.New(ctx);
}
std::shared_ptr<ScWindow> openWindow(DukValue desc)
static JSValue openWindow(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
using namespace OpenRCT2::Ui::Windows;
auto& execInfo = _scriptEngine.GetExecInfo();
ScriptEngine* scriptEngine = gScUi.GetOpaque<ScriptEngine*>(thisVal);
if (!scriptEngine)
{
JS_ThrowInternalError(ctx, "No script engine");
return JS_EXCEPTION;
}
auto& execInfo = scriptEngine->GetExecInfo();
auto owner = execInfo.GetCurrentPlugin();
owner->ThrowIfStopping();
if (owner->IsStopping())
{
JS_ThrowInternalError(ctx, "Plugin is stopping.");
return JS_EXCEPTION;
}
std::shared_ptr<ScWindow> scWindow = nullptr;
auto w = WindowCustomOpen(owner, desc);
auto w = WindowCustomOpen(ctx, owner, argv[0]);
if (w != nullptr)
{
scWindow = std::make_shared<ScWindow>(w);
return gScWindow.New(ctx, w);
}
return scWindow;
return JS_NULL;
}
void closeWindows(std::string classification, DukValue id)
static JSValue closeWindows(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_STR(classification, ctx, argv[0]);
JSValue id = argv[1];
auto* windowMgr = Ui::GetWindowManager();
auto cls = GetClassification(classification);
if (cls != WindowClass::null)
{
if (id.type() == DukValue::Type::NUMBER)
if (JS_IsNumber(id))
{
windowMgr->CloseByNumber(cls, id.as_uint());
windowMgr->CloseByNumber(cls, static_cast<WindowNumber>(JSToInt(ctx, id)));
}
else
{
windowMgr->CloseByClass(cls);
}
}
return JS_UNDEFINED;
}
void closeAllWindows()
static JSValue closeAllWindows(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto* windowMgr = Ui::GetWindowManager();
windowMgr->CloseAll();
return JS_UNDEFINED;
}
std::shared_ptr<ScWindow> getWindow(DukValue a) const
static JSValue getWindow(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
if (a.type() == DukValue::Type::NUMBER)
if (JS_IsNumber(argv[0]))
{
auto index = a.as_uint();
size_t i = 0;
int32_t index = -1;
JS_ToInt32(ctx, &index, argv[0]);
int32_t i = 0;
for (const auto& w : gWindowList)
{
if (i == index)
{
return std::make_shared<ScWindow>(w.get());
return gScWindow.New(ctx, w.get());
}
i++;
}
}
else if (a.type() == DukValue::Type::STRING)
else if (JS_IsString(argv[0]))
{
const auto& classification = a.as_string();
auto w = FindCustomWindowByClassification(classification);
std::string classification = JSToStdString(ctx, argv[0]);
auto w = Ui::Windows::FindCustomWindowByClassification(classification);
if (w != nullptr)
{
return std::make_shared<ScWindow>(w);
return gScWindow.New(ctx, w);
}
}
return {};
return JS_NULL;
}
void showError(const std::string& title, const std::string& message)
static JSValue showError(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
ErrorOpen(title, message);
if (argc < 2 || !JS_IsString(argv[0]) || !JS_IsString(argv[1]))
{
JS_ThrowTypeError(ctx, "Invalid arguments");
return JS_EXCEPTION;
}
std::string title = JSToStdString(ctx, argv[0]);
std::string message = JSToStdString(ctx, argv[1]);
Ui::Windows::ErrorOpen(title, message);
return JS_UNDEFINED;
}
void showTextInput(const DukValue& desc)
static JSValue showTextInput(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
try
JSValue desc = argv[0];
if (!JS_IsObject(desc))
{
constexpr int32_t kMaxLengthAllowed = 4096;
auto plugin = _scriptEngine.GetExecInfo().GetCurrentPlugin();
auto title = desc["title"].as_string();
auto description = desc["description"].as_string();
auto initialValue = AsOrDefault(desc["initialValue"], "");
auto maxLength = AsOrDefault(desc["maxLength"], kMaxLengthAllowed);
auto callback = desc["callback"];
WindowTextInputOpen(
title, description, initialValue, std::clamp(maxLength, 0, kMaxLengthAllowed),
[this, plugin, callback](std::string_view value) {
auto dukValue = ToDuk(_scriptEngine.GetContext(), value);
_scriptEngine.ExecutePluginCall(plugin, callback, { dukValue }, false);
},
{});
JS_ThrowInternalError(ctx, "Invalid parameters.");
return JS_EXCEPTION;
}
catch (const DukException&)
ScriptEngine* scriptEngine = gScUi.GetOpaque<ScriptEngine*>(thisVal);
if (!scriptEngine)
{
duk_error(_scriptEngine.GetContext(), DUK_ERR_ERROR, "Invalid parameters.");
JS_ThrowInternalError(ctx, "No script engine");
return JS_EXCEPTION;
}
constexpr int32_t kMaxLengthAllowed = 4096;
auto plugin = scriptEngine->GetExecInfo().GetCurrentPlugin();
auto title = JSToStdString(ctx, desc, "title");
auto description = JSToStdString(ctx, desc, "description");
auto initialValue = AsOrDefault(ctx, desc, "initialValue", "");
auto maxLength = AsOrDefault(ctx, desc, "maxLength", kMaxLengthAllowed);
auto callback = JSToCallback(ctx, desc, "callback");
Ui::Windows::WindowTextInputOpen(
title, description, initialValue, std::clamp(maxLength, 0, kMaxLengthAllowed),
[scriptEngine, ctx, plugin, callback](std::string_view value) {
scriptEngine->ExecutePluginCall(plugin, callback.callback, { JSFromStdString(ctx, value) }, false);
},
{});
return JS_UNDEFINED;
}
void showFileBrowse(const DukValue& desc)
static JSValue showFileBrowse(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
try
JSValue desc = argv[0];
if (!JS_IsObject(desc))
{
auto plugin = _scriptEngine.GetExecInfo().GetCurrentPlugin();
auto type = desc["type"].as_string();
auto fileType = desc["fileType"].as_string();
auto defaultPath = AsOrDefault(desc["defaultPath"], "");
auto callback = desc["callback"];
auto loadSaveAction = LoadSaveAction::load;
if (type == "load")
loadSaveAction = LoadSaveAction::load;
else
throw DukException();
LoadSaveType loadSaveType;
if (fileType == "game")
loadSaveType = LoadSaveType::park;
else if (fileType == "heightmap")
loadSaveType = LoadSaveType::heightmap;
else
throw DukException();
LoadsaveOpen(
loadSaveAction, loadSaveType, defaultPath,
[this, plugin, callback](ModalResult result, std::string_view path) {
if (result == ModalResult::ok)
{
auto dukValue = ToDuk(_scriptEngine.GetContext(), path);
_scriptEngine.ExecutePluginCall(plugin, callback, { dukValue }, false);
}
},
nullptr);
JS_ThrowInternalError(ctx, "Invalid parameters.");
return JS_EXCEPTION;
}
catch (const DukException&)
ScriptEngine* scriptEngine = gScUi.GetOpaque<ScriptEngine*>(thisVal);
if (!scriptEngine)
{
duk_error(_scriptEngine.GetContext(), DUK_ERR_ERROR, "Invalid parameters.");
JS_ThrowInternalError(ctx, "No script engine");
return JS_EXCEPTION;
}
auto plugin = scriptEngine->GetExecInfo().GetCurrentPlugin();
auto type = JSToStdString(ctx, desc, "type");
auto fileType = JSToStdString(ctx, desc, "fileType");
auto defaultPath = AsOrDefault(ctx, desc, "defaultPath", "");
auto callback = JSToCallback(ctx, desc, "callback");
auto loadSaveAction = LoadSaveAction::load;
if (type == "load")
loadSaveAction = LoadSaveAction::load;
else
{
JS_ThrowPlainError(ctx, "Invalid type parameter");
return JS_EXCEPTION;
}
LoadSaveType loadSaveType;
if (fileType == "game")
loadSaveType = LoadSaveType::park;
else if (fileType == "heightmap")
loadSaveType = LoadSaveType::heightmap;
else
{
JS_ThrowPlainError(ctx, "Invalid fileType parameter");
return JS_EXCEPTION;
}
Ui::Windows::LoadsaveOpen(
loadSaveAction, loadSaveType, defaultPath,
[scriptEngine, ctx, plugin, callback](ModalResult result, std::string_view path) {
if (result == ModalResult::ok)
{
scriptEngine->ExecutePluginCall(plugin, callback.callback, { JSFromStdString(ctx, path) }, false);
}
},
true, nullptr);
return JS_UNDEFINED;
}
void showScenarioSelect(const DukValue& desc)
static JSValue showScenarioSelect(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto plugin = _scriptEngine.GetExecInfo().GetCurrentPlugin();
auto callback = desc["callback"];
JSValue desc = argv[0];
if (!JS_IsObject(desc))
{
JS_ThrowInternalError(ctx, "Invalid parameters.");
return JS_EXCEPTION;
}
ScenarioselectOpen([this, plugin, callback](std::string_view path) {
auto dukValue = GetScenarioFile(path);
_scriptEngine.ExecutePluginCall(plugin, callback, { dukValue }, false);
ScriptEngine* scriptEngine = gScUi.GetOpaque<ScriptEngine*>(thisVal);
if (!scriptEngine)
{
JS_ThrowInternalError(ctx, "No script engine");
return JS_EXCEPTION;
}
auto plugin = scriptEngine->GetExecInfo().GetCurrentPlugin();
auto callback = JSToCallback(ctx, desc, "callback");
Ui::Windows::ScenarioselectOpen([scriptEngine, ctx, plugin, callback](std::string_view path) {
scriptEngine->ExecutePluginCall(plugin, callback.callback, { GetScenarioFile(ctx, path) }, false);
});
return JS_UNDEFINED;
}
void activateTool(const DukValue& desc)
static JSValue activateTool(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
InitialiseCustomTool(_scriptEngine, desc);
ScriptEngine* scriptEngine = gScUi.GetOpaque<ScriptEngine*>(thisVal);
if (!scriptEngine)
{
JS_ThrowInternalError(ctx, "No script engine");
return JS_EXCEPTION;
}
return InitialiseCustomTool(*scriptEngine, ctx, argv[0]);
}
void registerMenuItem(std::string text, DukValue callback)
static JSValue registerMenuItem(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto& execInfo = _scriptEngine.GetExecInfo();
ScriptEngine* scriptEngine = gScUi.GetOpaque<ScriptEngine*>(thisVal);
if (!scriptEngine)
{
JS_ThrowInternalError(ctx, "No script engine");
return JS_EXCEPTION;
}
if (argc < 2 || !JS_IsString(argv[0]) || !JS_IsFunction(ctx, argv[1]))
{
JS_ThrowTypeError(ctx, "Invalid arguments");
return JS_EXCEPTION;
}
auto& execInfo = scriptEngine->GetExecInfo();
auto owner = execInfo.GetCurrentPlugin();
CustomMenuItems.emplace_back(owner, CustomToolbarMenuItemKind::Standard, text, callback);
std::string text = JSToStdString(ctx, argv[0]);
assert(owner->GetContext() == ctx);
CustomMenuItems.emplace_back(owner, CustomToolbarMenuItemKind::Standard, text, JSCallback(ctx, argv[1]));
std::ranges::sort(CustomMenuItems, [](auto&& a, auto&& b) { return a.Text < b.Text; });
return JS_UNDEFINED;
}
void registerToolboxMenuItem(const std::string& text, DukValue callback)
static JSValue registerToolboxMenuItem(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto& execInfo = _scriptEngine.GetExecInfo();
ScriptEngine* scriptEngine = gScUi.GetOpaque<ScriptEngine*>(thisVal);
if (!scriptEngine)
{
JS_ThrowInternalError(ctx, "No script engine");
return JS_EXCEPTION;
}
if (argc < 2 || !JS_IsString(argv[0]) || !JS_IsFunction(ctx, argv[1]))
{
JS_ThrowTypeError(ctx, "Invalid arguments");
return JS_EXCEPTION;
}
auto& execInfo = scriptEngine->GetExecInfo();
auto owner = execInfo.GetCurrentPlugin();
if (owner->GetMetadata().Type == PluginType::Intransient)
{
CustomMenuItems.emplace_back(owner, CustomToolbarMenuItemKind::Toolbox, text, callback);
CustomMenuItems.emplace_back(
owner, CustomToolbarMenuItemKind::Toolbox, JSToStdString(ctx, argv[0]), JSCallback(ctx, argv[1]));
std::ranges::sort(CustomMenuItems, [](auto&& a, auto&& b) { return a.Text < b.Text; });
}
else
{
duk_error(_scriptEngine.GetContext(), DUK_ERR_ERROR, "Plugin must be intransient.");
JS_ThrowPlainError(ctx, "Plugin must be intransient.");
return JS_EXCEPTION;
}
return JS_UNDEFINED;
}
void registerShortcut(DukValue desc)
static JSValue registerShortcut(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
try
JSValue desc = argv[0];
if (!JS_IsObject(desc))
{
auto& execInfo = _scriptEngine.GetExecInfo();
auto owner = execInfo.GetCurrentPlugin();
auto id = desc["id"].as_string();
auto text = desc["text"].as_string();
std::vector<std::string> bindings;
auto dukBindings = desc["bindings"];
if (dukBindings.is_array())
{
for (auto binding : dukBindings.as_array())
{
bindings.push_back(binding.as_string());
}
}
auto callback = desc["callback"];
CustomShortcuts.emplace_back(std::make_unique<CustomShortcut>(owner, id, text, bindings, callback));
JS_ThrowInternalError(ctx, "Invalid parameters.");
return JS_EXCEPTION;
}
catch (const DukException&)
ScriptEngine* scriptEngine = gScUi.GetOpaque<ScriptEngine*>(thisVal);
if (!scriptEngine)
{
duk_error(_scriptEngine.GetContext(), DUK_ERR_ERROR, "Invalid parameters.");
JS_ThrowInternalError(ctx, "No script engine");
return JS_EXCEPTION;
}
auto owner = scriptEngine->GetExecInfo().GetCurrentPlugin();
auto id = JSToStdString(ctx, desc, "id");
auto text = JSToStdString(ctx, desc, "text");
std::vector<std::string> bindings;
JSIterateArray(
ctx, desc, "bindings", [&bindings](JSContext* ctx2, JSValue x) { bindings.push_back(JSToStdString(ctx2, x)); });
auto callback = JSToCallback(ctx, desc, "callback");
CustomShortcuts.emplace_back(std::make_unique<CustomShortcut>(owner, id, text, bindings, callback));
return JS_UNDEFINED;
}
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("height", ScUi::height_get, nullptr),
JS_CGETSET_DEF("width", ScUi::width_get, nullptr),
JS_CGETSET_DEF("windows", ScUi::windows_get, nullptr),
JS_CGETSET_DEF("mainViewport", ScUi::mainViewport_get, nullptr),
JS_CGETSET_DEF("tileSelection", ScUi::tileSelection_get, nullptr),
JS_CGETSET_DEF("tool", ScUi::tool_get, nullptr),
JS_CGETSET_DEF("imageManager", ScUi::imageManager_get, nullptr),
JS_CFUNC_DEF("openWindow", 1, ScUi::openWindow),
JS_CFUNC_DEF("closeWindows", 2, ScUi::closeWindows),
JS_CFUNC_DEF("closeAllWindows", 0, ScUi::closeAllWindows),
JS_CFUNC_DEF("getWindow", 1, ScUi::getWindow),
JS_CFUNC_DEF("showError", 2, ScUi::showError),
JS_CFUNC_DEF("showTextInput", 1, ScUi::showTextInput),
JS_CFUNC_DEF("showFileBrowse", 1, ScUi::showFileBrowse),
JS_CFUNC_DEF("showScenarioSelect", 1, ScUi::showScenarioSelect),
JS_CFUNC_DEF("activateTool", 1, ScUi::activateTool),
JS_CFUNC_DEF("registerMenuItem", 2, ScUi::registerMenuItem),
JS_CFUNC_DEF("registerToolboxMenuItem", 2, ScUi::registerToolboxMenuItem),
JS_CFUNC_DEF("registerShortcut", 1, ScUi::registerShortcut),
};
public:
static void Register(duk_context* ctx)
JSValue New(JSContext* ctx, ScriptEngine* scriptEngine)
{
dukglue_register_property(ctx, &ScUi::height_get, nullptr, "height");
dukglue_register_property(ctx, &ScUi::width_get, nullptr, "width");
dukglue_register_property(ctx, &ScUi::windows_get, nullptr, "windows");
dukglue_register_property(ctx, &ScUi::mainViewport_get, nullptr, "mainViewport");
dukglue_register_property(ctx, &ScUi::tileSelection_get, nullptr, "tileSelection");
dukglue_register_property(ctx, &ScUi::tool_get, nullptr, "tool");
dukglue_register_property(ctx, &ScUi::imageManager_get, nullptr, "imageManager");
dukglue_register_method(ctx, &ScUi::openWindow, "openWindow");
dukglue_register_method(ctx, &ScUi::closeWindows, "closeWindows");
dukglue_register_method(ctx, &ScUi::closeAllWindows, "closeAllWindows");
dukglue_register_method(ctx, &ScUi::getWindow, "getWindow");
dukglue_register_method(ctx, &ScUi::showError, "showError");
dukglue_register_method(ctx, &ScUi::showTextInput, "showTextInput");
dukglue_register_method(ctx, &ScUi::showFileBrowse, "showFileBrowse");
dukglue_register_method(ctx, &ScUi::showScenarioSelect, "showScenarioSelect");
dukglue_register_method(ctx, &ScUi::activateTool, "activateTool");
dukglue_register_method(ctx, &ScUi::registerMenuItem, "registerMenuItem");
dukglue_register_method(ctx, &ScUi::registerToolboxMenuItem, "registerToolboxMenuItem");
dukglue_register_method(ctx, &ScUi::registerShortcut, "registerShortcut");
return MakeWithOpaque(ctx, funcs, scriptEngine);
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Ui");
}
private:
WindowClass GetClassification(const std::string& key) const
static WindowClass GetClassification(const std::string& key)
{
return WindowClass::null;
}
DukValue GetScenarioFile(std::string_view path)
static JSValue GetScenarioFile(JSContext* ctx, std::string_view path)
{
auto ctx = _scriptEngine.GetContext();
DukObject obj(ctx);
obj.Set("path", path);
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "path", JSFromStdString(ctx, path));
auto* scenarioRepo = GetScenarioRepository();
auto entry = scenarioRepo->GetByPath(std::string(path).c_str());
if (entry != nullptr)
{
obj.Set("id", entry->ScenarioId);
obj.Set("category", ToDuk(ctx, entry->Category));
obj.Set("sourceGame", ToDuk(ctx, entry->SourceGame));
obj.Set("internalName", entry->InternalName);
obj.Set("name", entry->Name);
obj.Set("details", entry->Details);
JS_SetPropertyStr(ctx, obj, "id", JS_NewInt32(ctx, entry->ScenarioId));
JS_SetPropertyStr(ctx, obj, "category", ScenarioCategoryToJS(ctx, entry->Category));
JS_SetPropertyStr(ctx, obj, "sourceGame", ScenarioSourceToJS(ctx, entry->SourceGame));
JS_SetPropertyStr(ctx, obj, "internalName", JSFromStdString(ctx, entry->InternalName));
JS_SetPropertyStr(ctx, obj, "name", JSFromStdString(ctx, entry->Name));
JS_SetPropertyStr(ctx, obj, "details", JSFromStdString(ctx, entry->Details));
auto* highscore = entry->Highscore;
if (highscore == nullptr)
{
obj.Set("highscore", nullptr);
JS_SetPropertyStr(ctx, obj, "highscore", JS_NULL);
}
else
{
DukObject dukHighscore(ctx);
dukHighscore.Set("name", highscore->name);
dukHighscore.Set("companyValue", highscore->company_value);
obj.Set("highscore", dukHighscore.Take());
JSValue jsHighscore = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, jsHighscore, "name", JSFromStdString(ctx, highscore->name));
JS_SetPropertyStr(ctx, jsHighscore, "companyValue", JS_NewInt64(ctx, highscore->company_value));
JS_SetPropertyStr(ctx, obj, "highscore", jsHighscore);
}
}
return obj.Take();
return obj;
}
};
} // namespace OpenRCT2::Scripting
+164 -136
View File
@@ -13,252 +13,287 @@
#include "../interface/Window.h"
#include <memory>
#include <openrct2/Context.h>
#include <openrct2/interface/Viewport.h>
#include <openrct2/scripting/Duktape.hpp>
#include <openrct2/scripting/ScriptEngine.h>
#include <openrct2/ui/WindowManager.h>
#include <openrct2/world/Map.h>
namespace OpenRCT2::Scripting
{
class ScViewport
class ScViewport;
extern ScViewport gScViewport;
class ScViewport final : public ScBase
{
private:
WindowClass _class{};
WindowNumber _number{};
public:
ScViewport(WindowClass c, WindowNumber n = 0)
: _class(c)
, _number(n)
struct OpaqueWindowData
{
WindowClass _class{};
WindowNumber _number{};
};
static JSValue left_get(JSContext* ctx, JSValue thisVal)
{
auto viewport = GetViewport(thisVal);
if (viewport != nullptr)
{
return JS_NewInt32(ctx, viewport->viewPos.x);
}
return JS_NewInt32(ctx, 0);
}
static JSValue left_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_INT32(valueInt, ctx, value);
WindowBase* w;
auto viewport = GetViewport(thisVal, &w);
if (w != nullptr && viewport != nullptr)
{
SetViewLeftTop(w, viewport, valueInt, viewport->viewPos.y);
}
return JS_UNDEFINED;
}
private:
int32_t left_get() const
static JSValue top_get(JSContext* ctx, JSValue thisVal)
{
auto viewport = GetViewport();
auto viewport = GetViewport(thisVal);
if (viewport != nullptr)
{
return viewport->viewPos.x;
return JS_NewInt32(ctx, viewport->viewPos.y);
}
return 0;
return JS_NewInt32(ctx, 0);
}
void left_set(int32_t value)
static JSValue top_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
auto viewport = GetViewport();
if (viewport != nullptr)
JS_UNPACK_INT32(valueInt, ctx, value);
WindowBase* w;
auto viewport = GetViewport(thisVal, &w);
if (w != nullptr && viewport != nullptr)
{
SetViewLeftTop(value, viewport->viewPos.y);
SetViewLeftTop(w, viewport, viewport->viewPos.x, valueInt);
}
return JS_UNDEFINED;
}
int32_t top_get() const
static JSValue right_get(JSContext* ctx, JSValue thisVal)
{
auto viewport = GetViewport();
auto viewport = GetViewport(thisVal);
if (viewport != nullptr)
{
return viewport->viewPos.y;
return JS_NewInt32(ctx, viewport->viewPos.x + viewport->ViewWidth());
}
return 0;
return JS_NewInt32(ctx, 0);
}
void top_set(int32_t value)
static JSValue right_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
auto viewport = GetViewport();
if (viewport != nullptr)
JS_UNPACK_INT32(valueInt, ctx, value);
WindowBase* w;
auto viewport = GetViewport(thisVal, &w);
if (w != nullptr && viewport != nullptr)
{
SetViewLeftTop(viewport->viewPos.x, value);
SetViewLeftTop(w, viewport, valueInt - viewport->ViewWidth(), viewport->viewPos.y);
}
return JS_UNDEFINED;
}
int32_t right_get() const
static JSValue bottom_get(JSContext* ctx, JSValue thisVal)
{
auto viewport = GetViewport();
auto viewport = GetViewport(thisVal);
if (viewport != nullptr)
{
return viewport->viewPos.x + viewport->ViewWidth();
return JS_NewInt32(ctx, viewport->viewPos.y + viewport->ViewHeight());
}
return 0;
return JS_NewInt32(ctx, 0);
}
void right_set(int32_t value)
static JSValue bottom_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
auto viewport = GetViewport();
if (viewport != nullptr)
JS_UNPACK_INT32(valueInt, ctx, value);
WindowBase* w;
auto viewport = GetViewport(thisVal, &w);
if (w != nullptr && viewport != nullptr)
{
SetViewLeftTop(value - viewport->ViewWidth(), viewport->viewPos.y);
SetViewLeftTop(w, viewport, viewport->viewPos.x, valueInt - viewport->ViewHeight());
}
return JS_UNDEFINED;
}
int32_t bottom_get() const
static JSValue rotation_get(JSContext* ctx, JSValue thisVal)
{
auto viewport = GetViewport();
auto viewport = GetViewport(thisVal);
if (viewport != nullptr)
{
return viewport->viewPos.y + viewport->ViewHeight();
return JS_NewInt32(ctx, viewport->rotation);
}
return 0;
return JS_NewInt32(ctx, 0);
}
void bottom_set(int32_t value)
static JSValue rotation_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
auto viewport = GetViewport();
if (viewport != nullptr)
{
SetViewLeftTop(viewport->viewPos.x, value - viewport->ViewHeight());
}
}
JS_UNPACK_INT32(valueInt, ctx, value);
int32_t rotation_get() const
{
auto viewport = GetViewport();
if (viewport != nullptr)
if (valueInt >= 0 && valueInt < 4)
{
return viewport->rotation;
}
return 0;
}
void rotation_set(int32_t value)
{
if (value >= 0 && value < 4)
{
auto w = GetWindow();
auto w = GetWindow(thisVal);
if (w != nullptr)
{
while (w->viewport->rotation != value)
while (w->viewport->rotation != valueInt)
{
ViewportRotateSingle(w, 1);
}
}
}
return JS_UNDEFINED;
}
int32_t zoom_get() const
static JSValue zoom_get(JSContext* ctx, JSValue thisVal)
{
auto viewport = GetViewport();
auto viewport = GetViewport(thisVal);
if (viewport != nullptr)
{
return static_cast<int8_t>(viewport->zoom);
return JS_NewInt32(ctx, static_cast<int8_t>(viewport->zoom));
}
return 0;
return JS_NewInt32(ctx, 0);
}
void zoom_set(int32_t value)
static JSValue zoom_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
auto w = GetWindow();
JS_UNPACK_INT32(valueInt, ctx, value);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
auto i8Value = static_cast<int8_t>(value);
auto i8Value = static_cast<int8_t>(valueInt);
WindowZoomSet(*w, ZoomLevel{ i8Value }, false);
}
return JS_UNDEFINED;
}
uint32_t visibilityFlags_get() const
static JSValue visibilityFlags_get(JSContext* ctx, JSValue thisVal)
{
auto viewport = GetViewport();
auto viewport = GetViewport(thisVal);
if (viewport != nullptr)
{
return viewport->flags;
return JS_NewInt64(ctx, viewport->flags);
}
return 0;
return JS_NewInt32(ctx, 0);
}
void visibilityFlags_set(uint32_t value)
static JSValue visibilityFlags_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
auto w = GetWindow();
JS_UNPACK_UINT32(valueUint, ctx, value)
auto w = GetWindow(thisVal);
if (w != nullptr)
{
auto viewport = w->viewport;
if (viewport != nullptr)
{
if (viewport->flags != value)
if (viewport->flags != valueUint)
{
viewport->flags = value;
viewport->flags = valueUint;
w->invalidate();
}
}
}
return JS_UNDEFINED;
}
DukValue getCentrePosition() const
static JSValue getCentrePosition(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto viewport = GetViewport();
auto viewport = GetViewport(thisVal);
if (viewport != nullptr)
{
auto centre = viewport->viewPos + ScreenCoordsXY{ viewport->ViewWidth() / 2, viewport->ViewHeight() / 2 };
auto coords = ViewportPosToMapPos(centre, 24, viewport->rotation);
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto obj = duk_push_object(ctx);
duk_push_number(ctx, coords.x);
duk_put_prop_string(ctx, obj, "x");
duk_push_number(ctx, coords.y);
duk_put_prop_string(ctx, obj, "y");
return DukValue::take_from_stack(ctx);
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, coords.x));
JS_SetPropertyStr(ctx, obj, "y", JS_NewInt32(ctx, coords.y));
return obj;
}
return {};
return JS_UNDEFINED;
}
void moveTo(DukValue position)
static JSValue moveTo(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto w = GetWindow();
auto w = GetWindow(thisVal);
if (w != nullptr)
{
auto viewport = w->viewport;
if (viewport != nullptr)
{
auto coords = GetCoordsFromObject(position);
auto coords = GetCoordsFromObject(ctx, argv[0]);
if (coords)
{
auto screenCoords = Translate3DTo2DWithZ(viewport->rotation, *coords);
auto left = screenCoords.x - (viewport->ViewWidth() / 2);
auto top = screenCoords.y - (viewport->ViewHeight() / 2);
SetViewLeftTop(left, top);
SetViewLeftTop(w, viewport, left, top);
}
}
}
return JS_UNDEFINED;
}
void scrollTo(DukValue position)
static JSValue scrollTo(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto w = GetWindow();
auto w = GetWindow(thisVal);
if (w != nullptr)
{
auto coords = GetCoordsFromObject(position);
auto coords = GetCoordsFromObject(ctx, argv[0]);
if (coords)
{
WindowScrollToLocation(*w, *coords);
}
}
return JS_UNDEFINED;
}
public:
static void Register(duk_context* ctx)
JSValue New(JSContext* ctx, WindowClass wClass, WindowNumber wNum = 0)
{
dukglue_register_property(ctx, &ScViewport::left_get, &ScViewport::left_set, "left");
dukglue_register_property(ctx, &ScViewport::top_get, &ScViewport::top_set, "top");
dukglue_register_property(ctx, &ScViewport::right_get, &ScViewport::right_set, "right");
dukglue_register_property(ctx, &ScViewport::bottom_get, &ScViewport::bottom_set, "bottom");
dukglue_register_property(ctx, &ScViewport::rotation_get, &ScViewport::rotation_set, "rotation");
dukglue_register_property(ctx, &ScViewport::zoom_get, &ScViewport::zoom_set, "zoom");
dukglue_register_property(
ctx, &ScViewport::visibilityFlags_get, &ScViewport::visibilityFlags_set, "visibilityFlags");
dukglue_register_method(ctx, &ScViewport::getCentrePosition, "getCentrePosition");
dukglue_register_method(ctx, &ScViewport::moveTo, "moveTo");
dukglue_register_method(ctx, &ScViewport::scrollTo, "scrollTo");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("left", ScViewport::left_get, ScViewport::left_set),
JS_CGETSET_DEF("top", ScViewport::top_get, ScViewport::top_set),
JS_CGETSET_DEF("right", ScViewport::right_get, ScViewport::right_set),
JS_CGETSET_DEF("bottom", ScViewport::bottom_get, ScViewport::bottom_set),
JS_CGETSET_DEF("rotation", ScViewport::rotation_get, ScViewport::rotation_set),
JS_CGETSET_DEF("zoom", ScViewport::zoom_get, ScViewport::zoom_set),
JS_CGETSET_DEF("visibilityFlags", ScViewport::visibilityFlags_get, ScViewport::visibilityFlags_set),
JS_CFUNC_DEF("getCentrePosition", 0, ScViewport::getCentrePosition),
JS_CFUNC_DEF("moveTo", 1, ScViewport::moveTo),
JS_CFUNC_DEF("scrollTo", 1, ScViewport::scrollTo),
};
return MakeWithOpaque(ctx, funcs, new OpaqueWindowData{ wClass, wNum });
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Viewport", Finalize);
}
static void Finalize(JSRuntime* rt, JSValue thisVal)
{
OpaqueWindowData* data = gScViewport.GetOpaque<OpaqueWindowData*>(thisVal);
if (data)
delete data;
}
private:
WindowBase* GetWindow() const
static WindowBase* GetWindow(JSValue thisVal)
{
if (_class == WindowClass::mainWindow)
return WindowGetMain();
OpaqueWindowData* data = gScViewport.GetOpaque<OpaqueWindowData*>(thisVal);
if (!data)
return nullptr;
auto* windowMgr = Ui::GetWindowManager();
return windowMgr->FindByNumber(_class, _number);
return windowMgr->FindByNumber(data->_class, data->_number);
}
Viewport* GetViewport() const
static Viewport* GetViewport(JSValue thisVal, WindowBase** wOut = nullptr)
{
auto w = GetWindow();
auto w = GetWindow(thisVal);
if (wOut)
*wOut = w;
if (w != nullptr)
{
return w->viewport;
@@ -266,42 +301,35 @@ namespace OpenRCT2::Scripting
return nullptr;
}
void SetViewLeftTop(int32_t left, int32_t top)
static void SetViewLeftTop(WindowBase* w, Viewport* viewport, int32_t left, int32_t top)
{
auto w = GetWindow();
if (w != nullptr)
if (viewport->viewPos != ScreenCoordsXY(left, top))
{
auto viewport = w->viewport;
if (viewport != nullptr && viewport->viewPos != ScreenCoordsXY(left, top))
{
viewport->viewPos.x = left;
viewport->viewPos.y = top;
w->flags.unset(WindowFlag::scrollingToLocation);
w->savedViewPos.x = viewport->viewPos.x;
w->savedViewPos.y = viewport->viewPos.y;
viewport->Invalidate();
}
viewport->viewPos.x = left;
viewport->viewPos.y = top;
w->flags.unset(WindowFlag::scrollingToLocation);
w->savedViewPos.x = viewport->viewPos.x;
w->savedViewPos.y = viewport->viewPos.y;
viewport->Invalidate();
}
}
std::optional<CoordsXYZ> GetCoordsFromObject(DukValue position) const
static std::optional<CoordsXYZ> GetCoordsFromObject(JSContext* ctx, JSValue position)
{
if (position.type() == DukValue::Type::OBJECT)
if (JS_IsObject(position))
{
auto dukX = position["x"];
auto dukY = position["y"];
auto dukZ = position["z"];
if (dukX.type() == DukValue::Type::NUMBER && dukY.type() == DukValue::Type::NUMBER)
auto x = JSToOptionalInt(ctx, position, "x");
auto y = JSToOptionalInt(ctx, position, "y");
auto z = JSToOptionalInt(ctx, position, "z");
if (x.has_value() && y.has_value())
{
auto x = dukX.as_int();
auto y = dukY.as_int();
if (dukZ.type() == DukValue::Type::NUMBER)
if (z.has_value())
{
return CoordsXYZ(x, y, dukZ.as_int());
return CoordsXYZ(x.value(), y.value(), z.value());
}
auto z = TileElementHeight(CoordsXY(x, y));
return CoordsXYZ(x, y, z);
auto zTile = TileElementHeight(CoordsXY(x.value(), y.value()));
return CoordsXYZ(x.value(), y.value(), zTile);
}
}
return std::nullopt;
File diff suppressed because it is too large Load Diff
+406
View File
@@ -0,0 +1,406 @@
/*****************************************************************************
* Copyright (c) 2014-2026 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.
*****************************************************************************/
#ifdef ENABLE_SCRIPTING
#include "ScWindow.h"
#include "ScWidget.hpp"
#include <openrct2/interface/ColourWithFlags.h>
#include <openrct2/ui/WindowManager.h>
namespace OpenRCT2::Scripting
{
using namespace OpenRCT2::Ui::Windows;
extern ScWindow gScWindow;
using OpaqueWindowData = struct
{
WindowClass _class;
WindowNumber _number;
};
JSValue ScWindow::classification_get(JSContext* ctx, JSValue thisVal)
{
OpaqueWindowData* data = gScWindow.GetOpaque<OpaqueWindowData*>(thisVal);
return JS_NewInt32(ctx, static_cast<int32_t>(data->_class));
}
JSValue ScWindow::number_get(JSContext* ctx, JSValue thisVal)
{
OpaqueWindowData* data = gScWindow.GetOpaque<OpaqueWindowData*>(thisVal);
return JS_NewInt32(ctx, data->_number);
}
JSValue ScWindow::x_get(JSContext* ctx, JSValue thisVal)
{
auto w = GetWindow(thisVal);
if (w != nullptr)
{
return JS_NewInt32(ctx, w->windowPos.x);
}
return JS_NewInt32(ctx, 0);
}
JSValue ScWindow::x_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_INT32(valueInt, ctx, value);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
WindowSetPosition(*w, { valueInt, w->windowPos.y });
}
return JS_UNDEFINED;
}
JSValue ScWindow::y_get(JSContext* ctx, JSValue thisVal)
{
auto w = GetWindow(thisVal);
if (w != nullptr)
{
return JS_NewInt32(ctx, w->windowPos.y);
}
return JS_NewInt32(ctx, 0);
}
JSValue ScWindow::y_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_INT32(valueInt, ctx, value);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
WindowSetPosition(*w, { w->windowPos.x, valueInt });
}
return JS_UNDEFINED;
}
JSValue ScWindow::width_get(JSContext* ctx, JSValue thisVal)
{
auto w = GetWindow(thisVal);
if (w != nullptr)
{
return JS_NewInt32(ctx, w->width);
}
return JS_NewInt32(ctx, 0);
}
JSValue ScWindow::width_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_INT32(valueInt, ctx, value);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
if (w->canBeResized())
{
WindowResizeByDelta(*w, valueInt - w->width, 0);
}
else
{
WindowSetResize(*w, { valueInt, w->minHeight }, { valueInt, w->maxHeight });
}
}
return JS_UNDEFINED;
}
JSValue ScWindow::height_get(JSContext* ctx, JSValue thisVal)
{
auto w = GetWindow(thisVal);
if (w != nullptr)
{
return JS_NewInt32(ctx, w->height - w->getTitleBarDiffNormal());
}
return JS_NewInt32(ctx, 0);
}
JSValue ScWindow::height_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_INT32(valueInt, ctx, value);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
valueInt += w->getTitleBarDiffNormal();
if (w->canBeResized())
{
WindowResizeByDelta(*w, 0, valueInt - w->height);
}
else
{
WindowSetResize(*w, { w->minWidth, valueInt }, { w->maxWidth, valueInt });
}
}
return JS_UNDEFINED;
}
JSValue ScWindow::minWidth_get(JSContext* ctx, JSValue thisVal)
{
auto w = GetWindow(thisVal);
if (w != nullptr)
{
return JS_NewInt32(ctx, w->minWidth);
}
return JS_NewInt32(ctx, 0);
}
JSValue ScWindow::minWidth_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_INT32(valueInt, ctx, value);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
WindowSetResize(*w, { valueInt, w->minHeight }, { w->maxWidth, w->maxHeight });
}
return JS_UNDEFINED;
}
JSValue ScWindow::maxWidth_get(JSContext* ctx, JSValue thisVal)
{
auto w = GetWindow(thisVal);
if (w != nullptr)
{
return JS_NewInt32(ctx, w->maxWidth);
}
return JS_NewInt32(ctx, 0);
}
JSValue ScWindow::maxWidth_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_INT32(valueInt, ctx, value);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
WindowSetResize(*w, { w->minWidth, w->minHeight }, { valueInt, w->maxHeight });
}
return JS_UNDEFINED;
}
JSValue ScWindow::minHeight_get(JSContext* ctx, JSValue thisVal)
{
auto w = GetWindow(thisVal);
if (w != nullptr)
{
return JS_NewInt32(ctx, w->minHeight - w->getTitleBarDiffNormal());
}
return JS_NewInt32(ctx, 0);
}
JSValue ScWindow::minHeight_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_INT32(valueInt, ctx, value);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
valueInt += w->getTitleBarDiffNormal();
WindowSetResize(*w, { w->minWidth, valueInt }, { w->maxWidth, w->maxHeight });
}
return JS_UNDEFINED;
}
JSValue ScWindow::maxHeight_get(JSContext* ctx, JSValue thisVal)
{
auto w = GetWindow(thisVal);
if (w != nullptr)
{
return JS_NewInt32(ctx, w->maxHeight - w->getTitleBarDiffNormal());
}
return JS_NewInt32(ctx, 0);
}
JSValue ScWindow::maxHeight_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_INT32(valueInt, ctx, value);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
valueInt += w->getTitleBarDiffNormal();
WindowSetResize(*w, { w->minWidth, w->minHeight }, { w->maxWidth, valueInt });
}
return JS_UNDEFINED;
}
JSValue ScWindow::isSticky_get(JSContext* ctx, JSValue thisVal)
{
auto w = GetWindow(thisVal);
if (w != nullptr)
{
return JS_NewBool(ctx, w->flags.hasAny(WindowFlag::stickToBack, WindowFlag::stickToFront) != 0);
}
return JS_NewBool(ctx, false);
}
JSValue ScWindow::widgets_get(JSContext* ctx, JSValue thisVal)
{
JSValue result = JS_NewArray(ctx);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
for (WidgetIndex widgetIndex = 0; widgetIndex < w->widgets.size(); widgetIndex++)
{
JS_SetPropertyInt64(ctx, result, widgetIndex, gScWidget.New(ctx, w, widgetIndex));
}
}
return result;
}
JSValue ScWindow::colours_get(JSContext* ctx, JSValue thisVal)
{
JSValue result = JS_NewArray(ctx);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
int64_t i = 0;
for (auto c : w->colours)
{
auto colour = EnumValue(c.colour);
if (c.flags.has(ColourFlag::translucent))
colour |= kLegacyColourFlagTranslucent;
JS_SetPropertyInt64(ctx, result, i++, JS_NewInt32(ctx, colour));
}
}
return result;
}
JSValue ScWindow::colours_set(JSContext* ctx, JSValue thisVal, JSValue colours)
{
auto w = GetWindow(thisVal);
if (w != nullptr && JS_IsArray(colours))
{
int64_t coloursLen = -1;
JS_GetLength(ctx, colours, &coloursLen);
for (int64_t i = 0; i < std::ssize(w->colours); i++)
{
auto c = ColourWithFlags{ Drawing::Colour::black };
if (i < coloursLen)
{
JSValue elem = JS_GetPropertyInt64(ctx, colours, i);
if (JS_IsNumber(elem))
{
int32_t colorInt = -1;
JS_ToInt32(ctx, &colorInt, elem);
uint8_t colour = (colorInt & ~kLegacyColourFlagTranslucent) % Drawing::kColourNumTotal;
bool isTranslucent = (colorInt & kLegacyColourFlagTranslucent);
c.colour = static_cast<Drawing::Colour>(colour);
c.flags.set(ColourFlag::translucent, isTranslucent);
}
JS_FreeValue(ctx, elem);
}
w->colours[i] = c;
}
w->invalidate();
}
return JS_UNDEFINED;
}
JSValue ScWindow::title_get(JSContext* ctx, JSValue thisVal)
{
auto w = GetWindow(thisVal);
if (w != nullptr && w->classification == WindowClass::custom)
{
return JSFromStdString(ctx, GetWindowTitle(w));
}
return JSFromStdString(ctx, {});
}
JSValue ScWindow::title_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_STR(valueStr, ctx, value);
auto w = GetWindow(thisVal);
if (w != nullptr && w->classification == WindowClass::custom)
{
UpdateWindowTitle(w, valueStr);
}
return JS_UNDEFINED;
}
JSValue ScWindow::tabIndex_get(JSContext* ctx, JSValue thisVal)
{
auto w = GetWindow(thisVal);
if (w != nullptr && w->classification == WindowClass::custom)
{
return JS_NewInt32(ctx, w->page);
}
return JS_NewInt32(ctx, 0);
}
JSValue ScWindow::tabIndex_set(JSContext* ctx, JSValue thisVal, JSValue tab)
{
JS_UNPACK_INT32(tabNumber, ctx, tab);
auto w = GetWindow(thisVal);
if (w != nullptr && w->classification == WindowClass::custom)
{
UpdateWindowTab(w, tabNumber);
}
return JS_UNDEFINED;
}
JSValue ScWindow::close(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto w = GetWindow(thisVal);
if (w != nullptr)
{
auto* windowMgr = Ui::GetWindowManager();
windowMgr->Close(*w);
}
return JS_UNDEFINED;
}
JSValue ScWindow::findWidget(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_STR(name, ctx, argv[0]);
auto w = GetWindow(thisVal);
if (w != nullptr)
{
auto widgetIndex = FindWidgetIndexByName(w, name);
if (widgetIndex)
{
return gScWidget.New(ctx, w, *widgetIndex);
}
}
return JS_NULL;
}
JSValue ScWindow::bringToFront(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto* w = GetWindow(thisVal);
if (w != nullptr)
{
auto* windowMgr = Ui::GetWindowManager();
w = windowMgr->BringToFront(*w);
w->flash();
}
return JS_UNDEFINED;
}
JSValue ScWindow::New(JSContext* ctx, WindowBase* w)
{
return New(ctx, w->classification, w->number);
}
JSValue ScWindow::New(JSContext* ctx, WindowClass wClass, WindowNumber wNum)
{
return MakeWithOpaque(ctx, funcs, new OpaqueWindowData{ wClass, wNum });
}
void ScWindow::Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Window", Finalize);
}
void ScWindow::Finalize(JSRuntime* rt, JSValue thisVal)
{
OpaqueWindowData* data = gScWindow.GetOpaque<OpaqueWindowData*>(thisVal);
if (data)
delete data;
}
WindowBase* ScWindow::GetWindow(JSValue thisVal)
{
OpaqueWindowData* data = gScWindow.GetOpaque<OpaqueWindowData*>(thisVal);
if (!data)
return nullptr;
auto* windowMgr = Ui::GetWindowManager();
return windowMgr->FindByNumber(data->_class, data->_number);
}
} // namespace OpenRCT2::Scripting
#endif
+108
View File
@@ -0,0 +1,108 @@
/*****************************************************************************
* Copyright (c) 2014-2026 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 <openrct2/interface/WindowBase.h>
#include <openrct2/scripting/ScriptEngine.h>
#include <quickjs.h>
namespace OpenRCT2::Scripting
{
class ScWindow;
extern ScWindow gScWindow;
class ScWindow final : public ScBase
{
private:
static JSValue classification_get(JSContext* ctx, JSValue thisVal);
static JSValue number_get(JSContext* ctx, JSValue thisVal);
static JSValue x_get(JSContext* ctx, JSValue thisVal);
static JSValue x_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue y_get(JSContext* ctx, JSValue thisVal);
static JSValue y_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue width_get(JSContext* ctx, JSValue thisVal);
static JSValue width_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue height_get(JSContext* ctx, JSValue thisVal);
static JSValue height_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue minWidth_get(JSContext* ctx, JSValue thisVal);
static JSValue minWidth_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue maxWidth_get(JSContext* ctx, JSValue thisVal);
static JSValue maxWidth_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue minHeight_get(JSContext* ctx, JSValue thisVal);
static JSValue minHeight_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue maxHeight_get(JSContext* ctx, JSValue thisVal);
static JSValue maxHeight_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue isSticky_get(JSContext* ctx, JSValue thisVal);
static JSValue widgets_get(JSContext* ctx, JSValue thisVal);
static JSValue colours_get(JSContext* ctx, JSValue thisVal);
static JSValue colours_set(JSContext* ctx, JSValue thisVal, JSValue colours);
static JSValue title_get(JSContext* ctx, JSValue thisVal);
static JSValue title_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue tabIndex_get(JSContext* ctx, JSValue thisVal);
static JSValue tabIndex_set(JSContext* ctx, JSValue thisVal, JSValue tab);
static JSValue close(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue findWidget(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue bringToFront(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
public:
JSValue New(JSContext* ctx, WindowBase* w);
JSValue New(JSContext* ctx, WindowClass wClass, WindowNumber wNum);
void Register(JSContext* ctx);
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("classification", ScWindow::classification_get, nullptr),
JS_CGETSET_DEF("number", ScWindow::number_get, nullptr),
JS_CGETSET_DEF("x", ScWindow::x_get, ScWindow::x_set),
JS_CGETSET_DEF("y", ScWindow::y_get, ScWindow::y_set),
JS_CGETSET_DEF("width", ScWindow::width_get, ScWindow::width_set),
JS_CGETSET_DEF("height", ScWindow::height_get, ScWindow::height_set),
JS_CGETSET_DEF("minWidth", ScWindow::minWidth_get, ScWindow::minWidth_set),
JS_CGETSET_DEF("maxWidth", ScWindow::maxWidth_get, ScWindow::maxWidth_set),
JS_CGETSET_DEF("minHeight", ScWindow::minHeight_get, ScWindow::minHeight_set),
JS_CGETSET_DEF("maxHeight", ScWindow::maxHeight_get, ScWindow::maxHeight_set),
JS_CGETSET_DEF("isSticky", ScWindow::isSticky_get, nullptr),
JS_CGETSET_DEF("widgets", ScWindow::widgets_get, nullptr),
JS_CGETSET_DEF("colours", ScWindow::colours_get, ScWindow::colours_set),
JS_CGETSET_DEF("title", ScWindow::title_get, ScWindow::title_set),
JS_CGETSET_DEF("tabIndex", ScWindow::tabIndex_get, ScWindow::tabIndex_set),
JS_CFUNC_DEF("close", 0, ScWindow::close),
JS_CFUNC_DEF("findWidget", 1, ScWindow::findWidget),
JS_CFUNC_DEF("bringToFront", 0, ScWindow::bringToFront)
};
private:
static void Finalize(JSRuntime* rt, JSValue thisVal);
static WindowBase* GetWindow(JSValue thisVal);
};
} // namespace OpenRCT2::Scripting
#endif
-374
View File
@@ -1,374 +0,0 @@
/*****************************************************************************
* Copyright (c) 2014-2026 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 "ScWidget.hpp"
#include <openrct2/interface/ColourWithFlags.h>
#include <openrct2/interface/Window.h>
#include <openrct2/interface/WindowBase.h>
#include <openrct2/scripting/Duktape.hpp>
namespace OpenRCT2::Scripting
{
using namespace OpenRCT2::Ui::Windows;
class ScWindow
{
private:
WindowClass _class;
WindowNumber _number;
public:
ScWindow(WindowBase* w)
: ScWindow(w->classification, w->number)
{
}
ScWindow(WindowClass c, WindowNumber n)
: _class(c)
, _number(n)
{
}
int32_t classification_get() const
{
return static_cast<int32_t>(_class);
}
int32_t number_get() const
{
return static_cast<int32_t>(_number);
}
int32_t x_get() const
{
auto w = GetWindow();
if (w != nullptr)
{
return w->windowPos.x;
}
return 0;
}
void x_set(int32_t value)
{
auto w = GetWindow();
if (w != nullptr)
{
WindowSetPosition(*w, { value, w->windowPos.y });
}
}
int32_t y_get() const
{
auto w = GetWindow();
if (w != nullptr)
{
return w->windowPos.y;
}
return 0;
}
void y_set(int32_t value)
{
auto w = GetWindow();
if (w != nullptr)
{
WindowSetPosition(*w, { w->windowPos.x, value });
}
}
int32_t width_get() const
{
auto w = GetWindow();
if (w != nullptr)
{
return w->width;
}
return 0;
}
void width_set(int32_t value)
{
auto w = GetWindow();
if (w != nullptr)
{
if (w->canBeResized())
{
WindowResizeByDelta(*w, value - w->width, 0);
}
else
{
WindowSetResize(*w, { value, w->minHeight }, { value, w->maxHeight });
}
}
}
int32_t height_get() const
{
auto w = GetWindow();
if (w != nullptr)
{
return w->height - w->getTitleBarDiffNormal();
}
return 0;
}
void height_set(int32_t value)
{
auto w = GetWindow();
if (w != nullptr)
{
value += w->getTitleBarDiffNormal();
if (w->canBeResized())
{
WindowResizeByDelta(*w, 0, value - w->height);
}
else
{
WindowSetResize(*w, { w->minWidth, value }, { w->maxWidth, value });
}
}
}
int32_t minWidth_get() const
{
auto w = GetWindow();
if (w != nullptr)
{
return w->minWidth;
}
return 0;
}
void minWidth_set(int32_t value)
{
auto w = GetWindow();
if (w != nullptr)
{
WindowSetResize(*w, { value, w->minHeight }, { w->maxWidth, w->maxHeight });
}
}
int32_t maxWidth_get() const
{
auto w = GetWindow();
if (w != nullptr)
{
return w->maxWidth;
}
return 0;
}
void maxWidth_set(int32_t value)
{
auto w = GetWindow();
if (w != nullptr)
{
WindowSetResize(*w, { w->minWidth, w->minHeight }, { value, w->maxHeight });
}
}
int32_t minHeight_get() const
{
auto w = GetWindow();
if (w != nullptr)
{
return w->minHeight - w->getTitleBarDiffNormal();
}
return 0;
}
void minHeight_set(int32_t value)
{
auto w = GetWindow();
if (w != nullptr)
{
value += w->getTitleBarDiffNormal();
WindowSetResize(*w, { w->minWidth, value }, { w->maxWidth, w->maxHeight });
}
}
int32_t maxHeight_get() const
{
auto w = GetWindow();
if (w != nullptr)
{
return w->maxHeight - w->getTitleBarDiffNormal();
}
return 0;
}
void maxHeight_set(int32_t value)
{
auto w = GetWindow();
if (w != nullptr)
{
value += w->getTitleBarDiffNormal();
WindowSetResize(*w, { w->minWidth, w->minHeight }, { w->maxWidth, value });
}
}
bool isSticky_get() const
{
auto w = GetWindow();
if (w != nullptr)
{
return w->flags.hasAny(WindowFlag::stickToBack, WindowFlag::stickToFront);
}
return false;
}
std::vector<DukValue> widgets_get() const
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
std::vector<DukValue> result;
auto w = GetWindow();
if (w != nullptr)
{
for (WidgetIndex widgetIndex = 0; widgetIndex < w->widgets.size(); widgetIndex++)
{
result.push_back(ScWidget::ToDukValue(ctx, w, widgetIndex));
}
}
return result;
}
std::vector<int32_t> colours_get() const
{
std::vector<int32_t> result;
auto w = GetWindow();
if (w != nullptr)
{
result.reserve(std::size(w->colours));
for (auto c : w->colours)
{
auto colour = EnumValue(c.colour);
if (c.flags.has(ColourFlag::translucent))
colour |= kLegacyColourFlagTranslucent;
result.push_back(colour);
}
}
return result;
}
void colours_set(std::vector<int32_t> colours)
{
auto w = GetWindow();
if (w != nullptr)
{
for (size_t i = 0; i < std::size(w->colours); i++)
{
auto c = ColourWithFlags{ Drawing::Colour::black };
if (i < colours.size())
{
auto colour = (colours[i] & ~kLegacyColourFlagTranslucent) % Drawing::kColourNumTotal;
bool isTranslucent = (colours[i] & kLegacyColourFlagTranslucent);
c.colour = static_cast<Drawing::Colour>(colour);
c.flags.set(ColourFlag::translucent, isTranslucent);
}
w->colours[i] = c;
}
w->invalidate();
}
}
std::string title_get() const
{
auto w = GetWindow();
if (w != nullptr && w->classification == WindowClass::custom)
{
return GetWindowTitle(w);
}
return {};
}
void title_set(std::string value)
{
auto w = GetWindow();
if (w != nullptr && w->classification == WindowClass::custom)
{
UpdateWindowTitle(w, value);
}
}
int32_t tabIndex_get() const
{
auto w = GetWindow();
if (w != nullptr && w->classification == WindowClass::custom)
{
return w->page;
}
return 0;
}
void tabIndex_set(int32_t tab)
{
auto w = GetWindow();
if (w != nullptr && w->classification == WindowClass::custom)
{
UpdateWindowTab(w, tab);
}
}
void close()
{
auto w = GetWindow();
if (w != nullptr)
{
auto* windowMgr = Ui::GetWindowManager();
windowMgr->Close(*w);
}
}
DukValue findWidget(std::string name) const
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto w = GetWindow();
if (w != nullptr)
{
auto widgetIndex = FindWidgetIndexByName(w, name);
if (widgetIndex)
{
return ScWidget::ToDukValue(ctx, w, *widgetIndex);
}
}
return GetObjectAsDukValue<ScWidget>(ctx, nullptr);
}
void bringToFront()
{
auto* w = GetWindow();
if (w != nullptr)
{
auto* windowMgr = Ui::GetWindowManager();
w = windowMgr->BringToFront(*w);
w->flash();
}
}
static void Register(duk_context* ctx)
{
dukglue_register_property(ctx, &ScWindow::classification_get, nullptr, "classification");
dukglue_register_property(ctx, &ScWindow::number_get, nullptr, "number");
dukglue_register_property(ctx, &ScWindow::x_get, &ScWindow::x_set, "x");
dukglue_register_property(ctx, &ScWindow::y_get, &ScWindow::y_set, "y");
dukglue_register_property(ctx, &ScWindow::width_get, &ScWindow::width_set, "width");
dukglue_register_property(ctx, &ScWindow::height_get, &ScWindow::height_set, "height");
dukglue_register_property(ctx, &ScWindow::minWidth_get, &ScWindow::minWidth_set, "minWidth");
dukglue_register_property(ctx, &ScWindow::maxWidth_get, &ScWindow::maxWidth_set, "maxWidth");
dukglue_register_property(ctx, &ScWindow::minHeight_get, &ScWindow::minHeight_set, "minHeight");
dukglue_register_property(ctx, &ScWindow::maxHeight_get, &ScWindow::maxHeight_set, "maxHeight");
dukglue_register_property(ctx, &ScWindow::isSticky_get, nullptr, "isSticky");
dukglue_register_property(ctx, &ScWindow::widgets_get, nullptr, "widgets");
dukglue_register_property(ctx, &ScWindow::colours_get, &ScWindow::colours_set, "colours");
dukglue_register_property(ctx, &ScWindow::title_get, &ScWindow::title_set, "title");
dukglue_register_property(ctx, &ScWindow::tabIndex_get, &ScWindow::tabIndex_set, "tabIndex");
dukglue_register_method(ctx, &ScWindow::close, "close");
dukglue_register_method(ctx, &ScWindow::findWidget, "findWidget");
dukglue_register_method(ctx, &ScWindow::bringToFront, "bringToFront");
}
private:
WindowBase* GetWindow() const
{
auto* windowMgr = Ui::GetWindowManager();
return windowMgr->FindByNumber(_class, _number);
}
};
} // namespace OpenRCT2::Scripting
#endif
+51 -42
View File
@@ -19,66 +19,75 @@
#include "ScTitleSequence.hpp"
#include "ScUi.hpp"
#include "ScWidget.hpp"
#include "ScWindow.hpp"
#include "ScWindow.h"
#include <openrct2/scripting/ScriptEngine.h>
using namespace OpenRCT2::Scripting;
ScGraphicsContext OpenRCT2::Scripting::gScGraphicsContext;
ScImageManager OpenRCT2::Scripting::gScImageManager;
ScTileSelection OpenRCT2::Scripting::gScTileSelection;
ScTool OpenRCT2::Scripting::gScTool;
ScUi OpenRCT2::Scripting::gScUi;
ScViewport OpenRCT2::Scripting::gScViewport;
ScWidget OpenRCT2::Scripting::gScWidget;
ScTitleSequence OpenRCT2::Scripting::gScTitleSequence;
ScTitleSequenceManager OpenRCT2::Scripting::gScTitleSequenceManager;
ScTitleSequencePark OpenRCT2::Scripting::gScTitleSequencePark;
ScWindow OpenRCT2::Scripting::gScWindow;
static void InitialiseContext(JSContext* ctx)
{
JSValue glb = JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx, glb, "titleSequenceManager", gScTitleSequenceManager.New(ctx));
JS_SetPropertyStr(ctx, glb, "ui", gScUi.New(ctx, &OpenRCT2::GetContext()->GetScriptEngine()));
JS_FreeValue(ctx, glb);
}
void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
{
auto ctx = scriptEngine.GetContext();
JSContext* ctx = scriptEngine.GetContext();
dukglue_register_global(ctx, std::make_shared<ScTitleSequenceManager>(), "titleSequenceManager");
dukglue_register_global(ctx, std::make_shared<ScUi>(scriptEngine), "ui");
gScGraphicsContext.Register(ctx);
gScImageManager.Register(ctx);
gScTileSelection.Register(ctx);
gScTool.Register(ctx);
gScUi.Register(ctx);
gScViewport.Register(ctx);
ScGraphicsContext::Register(ctx);
ScImageManager::Register(ctx);
ScTileSelection::Register(ctx);
ScTool::Register(ctx);
ScUi::Register(ctx);
ScViewport::Register(ctx);
gScWidget.Register(ctx);
ScWidget::Register(ctx);
ScButtonWidget::Register(ctx);
ScColourPickerWidget::Register(ctx);
ScCheckBoxWidget::Register(ctx);
ScDropdownWidget::Register(ctx);
ScGroupBoxWidget::Register(ctx);
ScLabelWidget::Register(ctx);
ScListViewWidget::Register(ctx);
ScSpinnerWidget::Register(ctx);
ScTextBoxWidget::Register(ctx);
ScViewportWidget::Register(ctx);
ScTitleSequence::Register(ctx);
ScTitleSequenceManager::Register(ctx);
ScTitleSequencePark::Register(ctx);
ScWindow::Register(ctx);
gScTitleSequence.Register(ctx);
gScTitleSequenceManager.Register(ctx);
gScTitleSequencePark.Register(ctx);
gScWindow.Register(ctx);
InitialiseCustomImages(scriptEngine);
InitialiseCustomMenuItems(scriptEngine);
scriptEngine.SubscribeToPluginStoppedEvent(
[](std::shared_ptr<Plugin> plugin) -> void { CloseWindowsOwnedByPlugin(plugin); });
[](std::shared_ptr<Plugin> plugin) -> void { Ui::Windows::CloseWindowsOwnedByPlugin(plugin); });
scriptEngine.RegisterExtension(InitialiseContext, Unregister);
}
std::shared_ptr<ScWindow> ScWidget::window_get() const
void UiScriptExtensions::Unregister()
{
return std::make_shared<ScWindow>(_class, _number);
}
gScGraphicsContext.Unregister();
gScImageManager.Unregister();
gScTileSelection.Unregister();
gScTool.Unregister();
gScUi.Unregister();
gScViewport.Unregister();
void ScWidget::Register(duk_context* ctx)
{
dukglue_register_property(ctx, &ScWidget::window_get, nullptr, "window");
dukglue_register_property(ctx, &ScWidget::name_get, &ScWidget::name_set, "name");
dukglue_register_property(ctx, &ScWidget::type_get, nullptr, "type");
dukglue_register_property(ctx, &ScWidget::x_get, &ScWidget::x_set, "x");
dukglue_register_property(ctx, &ScWidget::y_get, &ScWidget::y_set, "y");
dukglue_register_property(ctx, &ScWidget::width_get, &ScWidget::width_set, "width");
dukglue_register_property(ctx, &ScWidget::height_get, &ScWidget::height_set, "height");
dukglue_register_property(ctx, &ScWidget::tooltip_get, &ScWidget::tooltip_set, "tooltip");
dukglue_register_property(ctx, &ScWidget::isDisabled_get, &ScWidget::isDisabled_set, "isDisabled");
dukglue_register_property(ctx, &ScWidget::isVisible_get, &ScWidget::isVisible_set, "isVisible");
gScWidget.Unregister();
gScTitleSequence.Unregister();
gScTitleSequenceManager.Unregister();
gScTitleSequencePark.Unregister();
gScWindow.Unregister();
}
#endif
+1
View File
@@ -19,6 +19,7 @@ namespace OpenRCT2::Scripting
{
public:
static void Extend(ScriptEngine& scriptEngine);
static void Unregister();
};
} // namespace OpenRCT2::Scripting
+5 -3
View File
@@ -607,6 +607,8 @@ namespace OpenRCT2::Ui::Windows
gGamePaused &= ~GAME_PAUSED_MODAL;
Audio::Resume();
}
UnregisterJSCallback();
}
void onResize() override
@@ -1159,14 +1161,12 @@ namespace OpenRCT2::Ui::Windows
};
WindowBase* LoadsaveOpen(
LoadSaveAction action, LoadSaveType type, std::string_view defaultPath, LoadSaveCallback callback,
LoadSaveAction action, LoadSaveType type, std::string_view defaultPath, LoadSaveCallback callback, bool isJsCallback,
TrackDesign* trackDesign)
{
_trackDesign = trackDesign;
_defaultPath = defaultPath;
RegisterCallback(callback);
auto* windowMgr = GetWindowManager();
auto* w = static_cast<LoadSaveWindow*>(windowMgr->BringToFrontByClass(WindowClass::loadsave));
if (w == nullptr)
@@ -1182,6 +1182,8 @@ namespace OpenRCT2::Ui::Windows
ScreenSize windowSize = { config.fileBrowserWidth, config.fileBrowserHeight };
RegisterCallback(callback, isJsCallback);
w = windowMgr->Create<LoadSaveWindow>(
WindowClass::loadsave, windowSize,
{ WindowFlag::stickToFront, WindowFlag::resizable, WindowFlag::autoPosition, WindowFlag::centreScreen }, action,
+1 -1
View File
@@ -138,7 +138,7 @@ namespace OpenRCT2::Ui::Windows
// LoadSave
WindowBase* LoadsaveOpen(
LoadSaveAction action, LoadSaveType type, std::string_view defaultPath,
std::function<void(ModalResult result, std::string_view)> callback, TrackDesign* trackDesign);
std::function<void(ModalResult result, std::string_view)> callback, bool isJsCallback, TrackDesign* trackDesign);
void WindowLoadSaveInputKey(WindowBase* w, uint32_t keycode);
// Main
+39 -39
View File
@@ -1,6 +1,6 @@
file(GLOB_RECURSE OPENRCT2_CORE_SOURCES "${CMAKE_CURRENT_LIST_DIR}/*.cpp")
file(GLOB_RECURSE OPENRCT2_CORE_HEADERS "${CMAKE_CURRENT_LIST_DIR}/*.h"
"${CMAKE_CURRENT_LIST_DIR}/*.hpp")
"${CMAKE_CURRENT_LIST_DIR}/*.hpp")
if (APPLE)
file(GLOB_RECURSE OPENRCT2_CORE_MM_SOURCES "${CMAKE_CURRENT_LIST_DIR}/*.mm")
@@ -8,14 +8,14 @@ if (APPLE)
endif ()
if (ENABLE_SCRIPTING)
include_directories("${CMAKE_CURRENT_LIST_DIR}/../thirdparty/duktape")
include_directories(SYSTEM "${CMAKE_CURRENT_LIST_DIR}/../thirdparty/quickjs-ng")
# duktape is third party, ignore all warnings
set(OPENRCT2_DUKTAPE_SOURCES "${CMAKE_CURRENT_LIST_DIR}/../thirdparty/duktape/duktape.cpp")
# quickjs-ng is third party, ignore all warnings
set(OPENRCT2_QUICKJS_NG_SOURCES "${CMAKE_CURRENT_LIST_DIR}/../thirdparty/quickjs-ng/quickjs-amalgam.c")
if (MSVC)
set_source_files_properties(${OPENRCT2_DUKTAPE_SOURCES} PROPERTIES COMPILE_FLAGS "/w")
set_source_files_properties(${OPENRCT2_QUICKJS_NG_SOURCES} PROPERTIES COMPILE_FLAGS "/w")
else ()
set_source_files_properties(${OPENRCT2_DUKTAPE_SOURCES} PROPERTIES COMPILE_FLAGS "-w")
set_source_files_properties(${OPENRCT2_QUICKJS_NG_SOURCES} PROPERTIES COMPILE_FLAGS "-w")
endif ()
endif ()
@@ -26,17 +26,17 @@ endif ()
# https://github.com/OpenRCT2/OpenRCT2/issues/24936
set(LIBOPENRCT2_LINKAGE)
if (APPLE)
set(LIBOPENRCT2_LINKAGE STATIC)
set(LIBOPENRCT2_LINKAGE STATIC)
endif()
add_library(libopenrct2 ${LIBOPENRCT2_LINKAGE} ${OPENRCT2_CORE_SOURCES} ${OPENRCT2_CORE_MM_SOURCES} ${OPENRCT2_DUKTAPE_SOURCES})
add_library(libopenrct2 ${LIBOPENRCT2_LINKAGE} ${OPENRCT2_CORE_SOURCES} ${OPENRCT2_CORE_MM_SOURCES} ${OPENRCT2_QUICKJS_NG_SOURCES})
add_library(OpenRCT2::libopenrct2 ALIAS libopenrct2)
if (CMAKE_COMPILER_IS_GNUCXX AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "13")
message(WARNING "Buggy GCC 12 detected! Disabling some warnings")
set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/localisation/FormatCodes.cpp" PROPERTIES COMPILE_FLAGS "-Wno-restrict")
set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/platform/Platform.Posix.cpp" PROPERTIES COMPILE_FLAGS "-Wno-restrict")
set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/ride/Ride.cpp" PROPERTIES COMPILE_FLAGS "-Wno-null-dereference")
set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/scenario/Scenario.cpp" PROPERTIES COMPILE_FLAGS "-Wno-restrict")
message(WARNING "Buggy GCC 12 detected! Disabling some warnings")
set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/localisation/FormatCodes.cpp" PROPERTIES COMPILE_FLAGS "-Wno-restrict")
set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/platform/Platform.Posix.cpp" PROPERTIES COMPILE_FLAGS "-Wno-restrict")
set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/ride/Ride.cpp" PROPERTIES COMPILE_FLAGS "-Wno-null-dereference")
set_source_files_properties("${CMAKE_CURRENT_LIST_DIR}/scenario/Scenario.cpp" PROPERTIES COMPILE_FLAGS "-Wno-restrict")
endif()
if (APPLE)
target_link_platform_libraries(libopenrct2)
@@ -152,23 +152,23 @@ endif ()
if (STATIC)
target_link_libraries(libopenrct2
${PNG_STATIC_LIBRARIES}
${ZLIB_STATIC_LIBRARIES}
${LIBZIP_STATIC_LIBRARIES}
${ZSTD_STATIC_LIBRARIES})
${PNG_STATIC_LIBRARIES}
${ZLIB_STATIC_LIBRARIES}
${LIBZIP_STATIC_LIBRARIES}
${ZSTD_STATIC_LIBRARIES})
else ()
if (NOT MSVC AND NOT EMSCRIPTEN)
target_link_libraries(libopenrct2
PkgConfig::PNG
PkgConfig::ZLIB
PkgConfig::LIBZIP
PkgConfig::ZSTD)
PkgConfig::PNG
PkgConfig::ZLIB
PkgConfig::LIBZIP
PkgConfig::ZSTD)
else ()
target_link_libraries(libopenrct2
${PNG_LIBRARIES}
${ZLIB_LIBRARIES}
${LIBZIP_LIBRARIES}
${ZSTD_LIBRARIES})
${PNG_LIBRARIES}
${ZLIB_LIBRARIES}
${LIBZIP_LIBRARIES}
${ZSTD_LIBRARIES})
endif ()
endif ()
@@ -247,7 +247,7 @@ endif()
# Includes
target_include_directories(libopenrct2 SYSTEM PRIVATE ${LIBZIP_INCLUDE_DIRS})
target_include_directories(libopenrct2 SYSTEM PRIVATE ${PNG_INCLUDE_DIRS}
${ZLIB_INCLUDE_DIRS})
${ZLIB_INCLUDE_DIRS})
target_include_directories(libopenrct2 SYSTEM PRIVATE ${ZSTD_INCLUDE_DIRS})
include_directories(libopenrct2 SYSTEM ${CMAKE_CURRENT_LIST_DIR}/../thirdparty)
@@ -256,20 +256,20 @@ include_directories(libopenrct2 SYSTEM ${CMAKE_CURRENT_LIST_DIR}/../thirdparty)
# definitions: Version.cpp and Crash/Platform.cpp
if (NOT OPENRCT2_VERSION_TAG STREQUAL "")
set_property(SOURCE ${CMAKE_CURRENT_LIST_DIR}/Version.cpp ${CMAKE_CURRENT_LIST_DIR}/Crash/Platform.cpp
PROPERTY COMPILE_DEFINITIONS
OPENRCT2_VERSION_TAG="${OPENRCT2_VERSION_TAG}")
PROPERTY COMPILE_DEFINITIONS
OPENRCT2_VERSION_TAG="${OPENRCT2_VERSION_TAG}")
endif()
if (NOT OPENRCT2_BRANCH STREQUAL "master" AND NOT OPENRCT2_BRANCH STREQUAL "")
set_property(SOURCE ${CMAKE_CURRENT_LIST_DIR}/Version.cpp ${CMAKE_CURRENT_LIST_DIR}/Crash/Platform.cpp
APPEND PROPERTY COMPILE_DEFINITIONS
OPENRCT2_BRANCH="${OPENRCT2_BRANCH}")
APPEND PROPERTY COMPILE_DEFINITIONS
OPENRCT2_BRANCH="${OPENRCT2_BRANCH}")
endif()
if (NOT OPENRCT2_COMMIT_SHA1_SHORT STREQUAL "HEAD" AND NOT OPENRCT2_COMMIT_SHA1_SHORT STREQUAL "")
set_property(SOURCE ${CMAKE_CURRENT_LIST_DIR}/Version.cpp ${CMAKE_CURRENT_LIST_DIR}/Crash/Platform.cpp
APPEND PROPERTY COMPILE_DEFINITIONS
OPENRCT2_COMMIT_SHA1_SHORT="${OPENRCT2_COMMIT_SHA1_SHORT}")
APPEND PROPERTY COMPILE_DEFINITIONS
OPENRCT2_COMMIT_SHA1_SHORT="${OPENRCT2_COMMIT_SHA1_SHORT}")
endif()
if((X86 OR X86_64) AND NOT MSVC AND NOT EMSCRIPTEN)
@@ -285,7 +285,7 @@ if (ENABLE_HEADERS_CHECK AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_library(libopenrct2-headers-check OBJECT ${OPENRCT2_CORE_HEADERS})
set_target_properties(libopenrct2-headers-check PROPERTIES LINKER_LANGUAGE CXX)
set_source_files_properties(${OPENRCT2_CORE_HEADERS} PROPERTIES LANGUAGE CXX)
add_definitions("-x c++ -Wno-pragma-once-outside-header -Wno-unused-const-variable")
target_compile_options(libopenrct2-headers-check PRIVATE -Wno-pragma-once-outside-header -Wno-unused-const-variable)
get_target_property(LIBOPENRCT2_INCLUDE_DIRS libopenrct2 INCLUDE_DIRECTORIES)
set_target_properties(libopenrct2-headers-check PROPERTIES INCLUDE_DIRECTORIES "${LIBOPENRCT2_INCLUDE_DIRS}")
else ()
@@ -299,12 +299,12 @@ endif ()
# Defines
target_compile_definitions(libopenrct2 PUBLIC
$<$<BOOL:${USE_MMAP}>:USE_MMAP>
$<$<BOOL:${DISABLE_NETWORK}>:DISABLE_NETWORK>
$<$<BOOL:${DISABLE_HTTP}>:DISABLE_HTTP>
$<$<BOOL:${DISABLE_TTF}>:DISABLE_TTF>
$<$<BOOL:${DISABLE_VERSION_CHECKER}>:DISABLE_VERSION_CHECKER>
$<$<BOOL:${ENABLE_SCRIPTING}>:ENABLE_SCRIPTING>
$<$<BOOL:${USE_MMAP}>:USE_MMAP>
$<$<BOOL:${DISABLE_NETWORK}>:DISABLE_NETWORK>
$<$<BOOL:${DISABLE_HTTP}>:DISABLE_HTTP>
$<$<BOOL:${DISABLE_TTF}>:DISABLE_TTF>
$<$<BOOL:${DISABLE_VERSION_CHECKER}>:DISABLE_VERSION_CHECKER>
$<$<BOOL:${ENABLE_SCRIPTING}>:ENABLE_SCRIPTING>
)
target_compile_options(libopenrct2 PUBLIC $<$<BOOL:${ENABLE_ASAN}>:-fsanitize=address>)
+1 -1
View File
@@ -208,7 +208,7 @@ namespace OpenRCT2
#endif
auto* windowMgr = GetWindowManager();
windowMgr->CloseAll();
windowMgr->Cleanup();
// Unload objects after closing all windows, this is to overcome windows like
// the object selection window which loads objects when closed.
+10 -11
View File
@@ -11,7 +11,6 @@
#include "../Context.h"
#include "../core/Guard.hpp"
#include "../scripting/Duktape.hpp"
#include "../scripting/HookEngine.h"
#include "../scripting/ScriptEngine.h"
#include "../world/Map.h"
@@ -39,21 +38,21 @@ namespace OpenRCT2::GameActions
auto ctx = GetContext()->GetScriptEngine().GetContext();
// Create event args object
auto obj = Scripting::DukObject(ctx);
obj.Set("x", coords.x);
obj.Set("y", coords.y);
obj.Set("player", _playerId);
obj.Set("type", EnumValue(_type));
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, coords.x));
JS_SetPropertyStr(ctx, obj, "y", JS_NewInt32(ctx, coords.y));
JS_SetPropertyStr(ctx, obj, "player", JS_NewInt32(ctx, _playerId));
JS_SetPropertyStr(ctx, obj, "type", JS_NewInt32(ctx, EnumValue(_type)));
auto flags = GetActionFlags();
obj.Set("isClientOnly", (flags & Flags::ClientOnly) != 0);
obj.Set("result", true);
JS_SetPropertyStr(ctx, obj, "isClientOnly", JS_NewBool(ctx, (flags & Flags::ClientOnly) != 0));
JS_SetPropertyStr(ctx, obj, "result", JS_NewBool(ctx, true));
// Call the subscriptions
auto e = obj.Take();
hookEngine.Call(Scripting::HookType::actionLocation, e, true);
hookEngine.Call(Scripting::HookType::actionLocation, obj, true, true);
auto scriptResult = Scripting::AsOrDefault(e["result"], true);
auto scriptResult = Scripting::AsOrDefault(ctx, obj, "result", true);
JS_FreeValue(ctx, obj);
return scriptResult;
}
@@ -23,7 +23,6 @@
#include "../platform/Platform.h"
#include "../profiling/Profiling.h"
#include "../scenario/Scenario.h"
#include "../scripting/Duktape.hpp"
#include "../scripting/HookEngine.h"
#include "../scripting/ScriptEngine.h"
#include "../ui/WindowManager.h"
@@ -8,7 +8,6 @@
*****************************************************************************/
#ifdef ENABLE_SCRIPTING
#include "CustomAction.h"
#include "../../Context.h"
@@ -23,17 +22,17 @@ namespace OpenRCT2::GameActions
{
}
std::string CustomAction::GetId() const
const std::string& CustomAction::GetId() const
{
return _id;
}
std::string CustomAction::GetJson() const
const std::string& CustomAction::GetJson() const
{
return _json;
}
std::string CustomAction::GetPluginName() const
const std::string& CustomAction::GetPluginName() const
{
return _pluginName;
}
+3 -3
View File
@@ -26,9 +26,9 @@ namespace OpenRCT2::GameActions
CustomAction() = default;
CustomAction(const std::string& id, const std::string& json, const std::string& pluginName);
std::string GetId() const;
std::string GetJson() const;
std::string GetPluginName() const;
const std::string& GetId() const;
const std::string& GetJson() const;
const std::string& GetPluginName() const;
uint16_t GetActionFlags() const override;
+2
View File
@@ -87,6 +87,8 @@ namespace OpenRCT2::File
}
else
{
// Reserve capacity for fsize + 1 to support the caller adding a null terminator if they want (needed for quickjs)
result.reserve(fsize + 1);
result.resize(static_cast<size_t>(fsize));
fstream.Read(result.data(), result.size());
}
+1 -9
View File
@@ -7416,15 +7416,7 @@ namespace OpenRCT2
auto& hookEngine = GetContext()->GetScriptEngine().GetHookEngine();
if (hookEngine.HasSubscriptions(Scripting::HookType::guestGeneration))
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
// Create event args object
auto obj = Scripting::DukObject(ctx);
obj.Set("id", peep->Id.ToUnderlying());
// Call the subscriptions
auto e = obj.Take();
hookEngine.Call(Scripting::HookType::guestGeneration, e, true);
hookEngine.Call(Scripting::HookType::guestGeneration, { { "id", peep->Id.ToUnderlying() } }, true);
}
#endif
+10 -5
View File
@@ -164,17 +164,22 @@ static constexpr float kWindowScrollLocations[][2] = {
}
}
/**
*
* rct2: 0x006E77A1
*/
void WindowUpdateAll()
void WindowCullDead()
{
// Remove all windows in gWindowList that have the WindowFlag::dead flag
gWindowList.erase(
std::remove_if(
gWindowList.begin(), gWindowList.end(), [](auto&& w) -> bool { return w->flags.has(WindowFlag::dead); }),
gWindowList.end());
}
/**
*
* rct2: 0x006E77A1
*/
void WindowUpdateAll()
{
WindowCullDead();
// Periodic update happens every second so 40 ticks.
if (gCurrentRealTimeTicks >= gWindowUpdateTicks)
+1
View File
@@ -295,6 +295,7 @@ namespace OpenRCT2
void WindowDispatchUpdateAll();
void WindowUpdateAllViewports();
void WindowCullDead();
void WindowUpdateAll();
void WindowNotifyLanguageChange();
+8 -6
View File
@@ -658,11 +658,11 @@
<ClInclude Include="scripting\bindings\world\ScTile.hpp" />
<ClInclude Include="scripting\bindings\world\ScTileElement.hpp" />
<ClInclude Include="scripting\bindings\world\ScWeather.hpp" />
<ClInclude Include="scripting\Duktape.hpp" />
<ClInclude Include="scripting\HookEngine.h" />
<ClInclude Include="scripting\IconNames.hpp" />
<ClInclude Include="scripting\Plugin.h" />
<ClInclude Include="scripting\ScriptEngine.h" />
<ClInclude Include="scripting\ScriptUtil.hpp" />
<ClInclude Include="scripting\SoundNames.hpp" />
<ClInclude Include="SpriteIds.h" />
<ClInclude Include="System.hpp" />
@@ -1199,6 +1199,7 @@
<ClCompile Include="scenes\title\TitleSequence.cpp" />
<ClCompile Include="scenes\title\TitleSequenceManager.cpp" />
<ClCompile Include="scripting\bindings\entity\ScBalloon.cpp" />
<ClCompile Include="scripting\bindings\entity\ScEntity.cpp" />
<ClCompile Include="scripting\bindings\entity\ScGuest.cpp" />
<ClCompile Include="scripting\bindings\entity\ScLitter.cpp" />
<ClCompile Include="scripting\bindings\entity\ScMoneyEffect.cpp" />
@@ -1261,11 +1262,12 @@
<ClCompile Include="world\tile_element\TrackElement.cpp" />
<ClCompile Include="world\tile_element\WallElement.cpp" />
<ClCompile Include="world\Wall.cpp" />
<ClCompile Include="..\thirdparty\duktape\duktape.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<ForcedIncludeFiles>
</ForcedIncludeFiles>
<ClCompile Include="..\thirdparty\quickjs-ng\quickjs-amalgam.c">
<CompileAs>CompileAsC</CompileAs>
<LanguageStandard_C>stdc11</LanguageStandard_C>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<SDLCheck>false</SDLCheck>
<AdditionalOptions>/experimental:c11atomics %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
</ItemGroup>
<ItemGroup>
@@ -1274,4 +1276,4 @@
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
</Project>
</Project>
+24 -25
View File
@@ -1900,18 +1900,19 @@ namespace OpenRCT2::Network
auto ctx = GetContext()->GetScriptEngine().GetContext();
// Create event args object
DukObject eObj(ctx);
eObj.Set("name", name);
eObj.Set("publicKeyHash", publicKeyHash);
eObj.Set("ipAddress", connection.Socket->GetIpAddress());
eObj.Set("cancel", false);
auto e = eObj.Take();
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "name", JSFromStdString(ctx, name));
JS_SetPropertyStr(ctx, obj, "publicKeyHash", JSFromStdString(ctx, publicKeyHash));
JS_SetPropertyStr(ctx, obj, "ipAddress", JSFromStdString(ctx, connection.Socket->GetIpAddress()));
JS_SetPropertyStr(ctx, obj, "cancel", JS_NewBool(ctx, false));
// Call the subscriptions
hookEngine.Call(HookType::networkAuthenticate, e, false);
hookEngine.Call(HookType::networkAuthenticate, obj, false, true);
// Check if any hook has cancelled the join
if (AsOrDefault(e["cancel"], false))
const bool canceled = AsOrDefault(ctx, obj, "cancel", false);
JS_FreeValue(ctx, obj);
if (canceled)
{
return false;
}
@@ -1931,12 +1932,11 @@ namespace OpenRCT2::Network
auto ctx = GetContext()->GetScriptEngine().GetContext();
// Create event args object
DukObject eObj(ctx);
eObj.Set("player", playerId);
auto e = eObj.Take();
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "player", JS_NewInt32(ctx, playerId));
// Call the subscriptions
hookEngine.Call(HookType::networkJoin, e, false);
hookEngine.Call(HookType::networkJoin, obj, false);
}
#endif
}
@@ -1952,12 +1952,11 @@ namespace OpenRCT2::Network
auto ctx = GetContext()->GetScriptEngine().GetContext();
// Create event args object
DukObject eObj(ctx);
eObj.Set("player", playerId);
auto e = eObj.Take();
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "player", JS_NewInt32(ctx, playerId));
// Call the subscriptions
hookEngine.Call(HookType::networkLeave, e, false);
hookEngine.Call(HookType::networkJoin, obj, false);
}
#endif
}
@@ -2902,23 +2901,23 @@ namespace OpenRCT2::Network
auto ctx = GetContext()->GetScriptEngine().GetContext();
// Create event args object
auto objIdx = duk_push_object(ctx);
duk_push_number(ctx, playerId);
duk_put_prop_string(ctx, objIdx, "player");
duk_push_string(ctx, text.c_str());
duk_put_prop_string(ctx, objIdx, "message");
auto e = DukValue::take_from_stack(ctx);
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "player", JS_NewInt32(ctx, playerId));
JS_SetPropertyStr(ctx, obj, "message", Scripting::JSFromStdString(ctx, text));
// Call the subscriptions
hookEngine.Call(Scripting::HookType::networkChat, e, false);
hookEngine.Call(Scripting::HookType::networkChat, obj, false, true);
// Update text from object if subscriptions changed it
if (e["message"].type() != DukValue::Type::STRING)
auto message = Scripting::JSToOptionalStdString(ctx, obj, "message");
JS_FreeValue(ctx, obj);
if (!message.has_value())
{
// Subscription set text to non-string, do not relay message
return false;
}
text = e["message"].as_string();
text = message.value();
if (text.empty())
{
// Subscription set text to empty string, do not relay message
+1 -1
View File
@@ -767,7 +767,7 @@ namespace OpenRCT2
{
#ifdef ENABLE_SCRIPTING
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.SetParkStorageFromJSON(gameState.pluginStorage);
scriptEngine.SetParkStorageFromJSON(gameState.pluginStorage, gameState.scenarioFileName);
#endif
}
}
+10 -10
View File
@@ -1079,19 +1079,19 @@ static void RideRatingsCalculate(RideRating::UpdateState& state, Ride& ride)
auto originalRatings = ride.ratings;
// Create event args object
auto obj = DukObject(ctx);
obj.Set("rideId", ride.id.ToUnderlying());
obj.Set("excitement", originalRatings.excitement);
obj.Set("intensity", originalRatings.intensity);
obj.Set("nausea", originalRatings.nausea);
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "rideId", JS_NewInt32(ctx, ride.id.ToUnderlying()));
JS_SetPropertyStr(ctx, obj, "excitement", JS_NewInt32(ctx, originalRatings.excitement));
JS_SetPropertyStr(ctx, obj, "intensity", JS_NewInt32(ctx, originalRatings.intensity));
JS_SetPropertyStr(ctx, obj, "nausea", JS_NewInt32(ctx, originalRatings.nausea));
// Call the subscriptions
auto e = obj.Take();
hookEngine.Call(HookType::rideRatingsCalculate, e, true);
hookEngine.Call(HookType::rideRatingsCalculate, obj, true, true);
auto scriptExcitement = AsOrDefault(e["excitement"], static_cast<int32_t>(originalRatings.excitement));
auto scriptIntensity = AsOrDefault(e["intensity"], static_cast<int32_t>(originalRatings.intensity));
auto scriptNausea = AsOrDefault(e["nausea"], static_cast<int32_t>(originalRatings.nausea));
auto scriptExcitement = AsOrDefault(ctx, obj, "excitement", static_cast<int32_t>(originalRatings.excitement));
auto scriptIntensity = AsOrDefault(ctx, obj, "intensity", static_cast<int32_t>(originalRatings.intensity));
auto scriptNausea = AsOrDefault(ctx, obj, "nausea", static_cast<int32_t>(originalRatings.nausea));
JS_FreeValue(ctx, obj);
ride.ratings.excitement = std::clamp<int32_t>(scriptExcitement, 0, INT16_MAX);
ride.ratings.intensity = std::clamp<int32_t>(scriptIntensity, 0, INT16_MAX);
+5 -6
View File
@@ -46,16 +46,15 @@ static void InvokeVehicleCrashHook(const EntityId vehicleId, const std::string_v
auto& hookEngine = GetContext()->GetScriptEngine().GetHookEngine();
if (hookEngine.HasSubscriptions(Scripting::HookType::vehicleCrash))
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
JSContext* ctx = GetContext()->GetScriptEngine().GetContext();
// Create event args object
auto obj = Scripting::DukObject(ctx);
obj.Set("id", vehicleId.ToUnderlying());
obj.Set("crashIntoType", crashId);
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "id", JS_NewInt64(ctx, vehicleId.ToUnderlying()));
JS_SetPropertyStr(ctx, obj, "crashIntoType", Scripting::JSFromStdString(ctx, crashId));
// Call the subscriptions
auto e = obj.Take();
hookEngine.Call(Scripting::HookType::vehicleCrash, e, true);
hookEngine.Call(Scripting::HookType::vehicleCrash, obj, true);
}
}
#endif
-504
View File
@@ -1,504 +0,0 @@
/*****************************************************************************
* Copyright (c) 2014-2026 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 <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, double 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 uint16_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<>
MapRange inline FromDuk(const DukValue& d)
{
MapRange range;
range.Point1 = FromDuk<CoordsXY>(d["leftTop"]);
range.Point2 = FromDuk<CoordsXY>(d["rightBottom"]);
return range.Normalise();
}
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 TileCoordsXY& 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 DukValue ToDuk(duk_context* ctx, const VehicleSpriteGroup& value)
{
DukObject dukSpriteGroup(ctx);
dukSpriteGroup.Set("imageId", value.imageId);
dukSpriteGroup.Set("spriteNumImages", OpenRCT2::Entity::Yaw::NumSpritesPrecision(value.spritePrecision));
return dukSpriteGroup.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 kObjectEntryIndexNull;
}
uint32_t ImageFromDuk(const DukValue& d);
} // namespace OpenRCT2::Scripting
#endif
+20 -28
View File
@@ -14,8 +14,6 @@
#include "../core/EnumMap.hpp"
#include "ScriptEngine.h"
#include <unordered_map>
using namespace OpenRCT2::Scripting;
static const EnumMap<HookType> HooksLookupTable(
@@ -54,7 +52,7 @@ HookEngine::HookEngine(ScriptEngine& scriptEngine)
}
}
uint32_t HookEngine::Subscribe(HookType type, std::shared_ptr<Plugin> owner, const DukValue& function)
uint32_t HookEngine::Subscribe(HookType type, const std::shared_ptr<Plugin>& owner, const JSCallback& function)
{
auto& hookList = GetHookList(type);
auto cookie = _nextCookie++;
@@ -76,7 +74,7 @@ void HookEngine::Unsubscribe(HookType type, uint32_t cookie)
}
}
void HookEngine::UnsubscribeAll(std::shared_ptr<const Plugin> owner)
void HookEngine::UnsubscribeAll(const std::shared_ptr<const Plugin>& owner)
{
for (auto& hookList : _hookMap)
{
@@ -115,51 +113,45 @@ void HookEngine::Call(HookType type, bool isGameStateMutable)
auto& hookList = GetHookList(type);
for (auto& hook : hookList.Hooks)
{
_scriptEngine.ExecutePluginCall(hook.Owner, hook.Function, {}, isGameStateMutable);
_scriptEngine.ExecutePluginCall(hook.Owner, hook.Function.callback, {}, isGameStateMutable);
}
}
void HookEngine::Call(HookType type, const DukValue& arg, bool isGameStateMutable)
void HookEngine::Call(HookType type, const JSValue arg, bool isGameStateMutable, bool keepArgsAlive)
{
auto& hookList = GetHookList(type);
for (auto& hook : hookList.Hooks)
{
_scriptEngine.ExecutePluginCall(hook.Owner, hook.Function, { arg }, isGameStateMutable);
_scriptEngine.ExecutePluginCall(hook.Owner, hook.Function.callback, { arg }, isGameStateMutable, keepArgsAlive);
}
}
void HookEngine::Call(
HookType type, const std::initializer_list<std::pair<std::string_view, std::any>>& args, bool isGameStateMutable)
HookType type, const std::initializer_list<std::pair<std::string, HookValue>>& args, bool isGameStateMutable)
{
auto& hookList = GetHookList(type);
for (auto& hook : hookList.Hooks)
{
auto ctx = _scriptEngine.GetContext();
JSContext* ctx = hook.Owner.get()->GetContext();
// Convert key/value pairs into an object
auto objIdx = duk_push_object(ctx);
JSValue obj = JS_NewObject(ctx);
for (const auto& arg : args)
{
if (arg.second.type() == typeid(int32_t))
{
auto val = std::any_cast<int32_t>(arg.second);
duk_push_int(ctx, val);
}
else if (arg.second.type() == typeid(std::string))
{
const auto& val = std::any_cast<std::string>(arg.second);
duk_push_string(ctx, val.c_str());
}
else
{
throw std::runtime_error("Not implemented");
}
duk_put_prop_string(ctx, objIdx, arg.first.data());
JSValue member = std::visit(
HookValuesToJS{
[ctx](int64_t v) { return JS_NewInt64(ctx, v); },
[ctx](uint32_t v) { return JS_NewInt64(ctx, v); },
[ctx](int32_t v) { return JS_NewInt32(ctx, v); },
[ctx](uint16_t v) { return JS_NewInt32(ctx, v); },
[ctx](int16_t v) { return JS_NewInt32(ctx, v); },
[ctx](const std::string& v) { return JSFromStdString(ctx, v); },
},
arg.second);
JS_SetPropertyStr(ctx, obj, arg.first.c_str(), member);
}
std::vector<DukValue> dukArgs;
dukArgs.push_back(DukValue::take_from_stack(ctx));
_scriptEngine.ExecutePluginCall(hook.Owner, hook.Function, dukArgs, isGameStateMutable);
_scriptEngine.ExecutePluginCall(hook.Owner, hook.Function.callback, { obj }, isGameStateMutable);
}
}
+16 -9
View File
@@ -11,12 +11,12 @@
#ifdef ENABLE_SCRIPTING
#include "Duktape.hpp"
#include "ScriptUtil.hpp"
#include <any>
#include <memory>
#include <string>
#include <tuple>
#include <variant>
#include <vector>
namespace OpenRCT2::Scripting
@@ -49,14 +49,22 @@ namespace OpenRCT2::Scripting
constexpr size_t NUM_HookTypeS = static_cast<size_t>(HookType::count);
HookType GetHookType(const std::string& name);
using HookValue = std::variant<int32_t, int16_t, uint16_t, std::string>;
template<class... Ts>
struct HookValuesToJS : Ts...
{
using Ts::operator()...;
};
struct Hook
{
uint32_t Cookie;
std::shared_ptr<Plugin> Owner;
DukValue Function;
JSCallback Function;
Hook() = default;
Hook(uint32_t cookie, std::shared_ptr<Plugin> owner, const DukValue& function)
Hook(uint32_t cookie, const std::shared_ptr<Plugin>& owner, const JSCallback& function)
: Cookie(cookie)
, Owner(owner)
, Function(function)
@@ -84,16 +92,15 @@ namespace OpenRCT2::Scripting
public:
HookEngine(ScriptEngine& scriptEngine);
HookEngine(const HookEngine&) = delete;
uint32_t Subscribe(HookType type, std::shared_ptr<Plugin> owner, const DukValue& function);
uint32_t Subscribe(HookType type, const std::shared_ptr<Plugin>& owner, const JSCallback& function);
void Unsubscribe(HookType type, uint32_t cookie);
void UnsubscribeAll(std::shared_ptr<const Plugin> owner);
void UnsubscribeAll(const std::shared_ptr<const Plugin>& owner);
void UnsubscribeAll();
bool HasSubscriptions(HookType type) const;
bool IsValidHookForPlugin(HookType type, Plugin& plugin) const;
void Call(HookType type, bool isGameStateMutable);
void Call(HookType type, const DukValue& arg, bool isGameStateMutable);
void Call(
HookType type, const std::initializer_list<std::pair<std::string_view, std::any>>& args, bool isGameStateMutable);
void Call(HookType type, JSValue arg, bool isGameStateMutable, bool keepArgsAlive = false);
void Call(HookType type, const std::initializer_list<std::pair<std::string, HookValue>>& args, bool isGameStateMutable);
private:
HookList& GetHookList(HookType type);
+97 -83
View File
@@ -11,10 +11,10 @@
#include "Plugin.h"
#include "../Context.h"
#include "../Diagnostic.h"
#include "../OpenRCT2.h"
#include "../core/File.h"
#include "Duktape.hpp"
#include "ScriptEngine.h"
#include <fstream>
@@ -22,9 +22,8 @@
using namespace OpenRCT2::Scripting;
Plugin::Plugin(duk_context* context, std::string_view path)
: _context(context)
, _path(path)
Plugin::Plugin(std::string_view path)
: _path(path)
{
}
@@ -33,45 +32,46 @@ void Plugin::SetCode(std::string_view code)
_code = code;
}
static JSValue registerPlugin(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
Plugin* plugin = static_cast<Plugin*>(JS_GetContextOpaque(ctx));
plugin->SetMetadata(argv[0]);
return JS_UNDEFINED;
}
void Plugin::Load()
{
if (_context)
{
JS_FreeContext(_context);
}
if (!_path.empty())
{
LoadCodeFromFile();
}
std::string projectedVariables = "console,context,date,map,network,park,profiler";
if (!gOpenRCT2Headless)
auto& scriptEngine = OpenRCT2::GetContext()->GetScriptEngine();
_context = scriptEngine.CreateContext();
JS_SetContextOpaque(_context, this);
JSValue registerFunc = JS_NewCFunction(_context, registerPlugin, "registerPlugin", 1);
JSValue glb = JS_GetGlobalObject(_context);
JS_SetPropertyStr(_context, glb, "registerPlugin", registerFunc);
JS_FreeValue(_context, glb);
JSValue res = JS_Eval(_context, _code.c_str(), _code.length(), _path.c_str(), JS_EVAL_TYPE_GLOBAL);
if (JS_IsException(res))
{
projectedVariables += ",ui";
JSValue exceptionVal = JS_GetException(_context);
std::string details = Stringify(_context, exceptionVal);
JS_FreeValue(_context, exceptionVal);
JS_FreeValue(_context, res);
JS_FreeContext(_context);
throw std::runtime_error("Failed to load plug-in script: " + _path + "\n" + details);
}
JS_FreeValue(_context, res);
// Wrap the script in a function and pass the global objects as variables
// so that if the script modifies them, they are not modified for other scripts.
// clang-format off
auto code = _code;
code =
" (function(" + projectedVariables + ") {"
" var __metadata__ = null;"
" var registerPlugin = function(m) { __metadata__ = m };"
" (function(__metadata__) {"
+ code +
" })();"
" return __metadata__;"
" })(" + projectedVariables + ");";
// clang-format on
auto flags = DUK_COMPILE_EVAL | DUK_COMPILE_SAFE | DUK_COMPILE_NOSOURCE | DUK_COMPILE_NOFILENAME;
auto result = duk_eval_raw(_context, code.c_str(), code.size(), flags);
if (result != DUK_ERR_NONE)
{
auto val = std::string(duk_safe_to_string(_context, -1));
duk_pop(_context);
throw std::runtime_error("Failed to load plug-in script: " + val + " at " + _path);
}
_metadata = GetMetadata(DukValue::take_from_stack(_context));
_hasLoaded = true;
}
@@ -82,23 +82,24 @@ void Plugin::Start()
throw std::runtime_error("Plugin has not been loaded.");
}
const auto& mainFunc = _metadata.Main;
if (mainFunc.context() == nullptr)
const JSValue mainFunc = _metadata.Main.callback;
if (!JS_IsFunction(_context, mainFunc))
{
throw std::runtime_error("No main function specified.");
}
_hasStarted = true;
mainFunc.push();
auto result = duk_pcall(_context, 0);
if (result != DUK_ERR_NONE)
const JSValue res = JS_Call(_context, mainFunc, JS_UNDEFINED, 0, nullptr);
if (JS_IsException(res))
{
auto val = std::string(duk_safe_to_string(_context, -1));
duk_pop(_context);
throw std::runtime_error("[" + _metadata.Name + "] " + val);
JSValue exceptionVal = JS_GetException(_context);
std::string details = Stringify(_context, exceptionVal);
JS_FreeValue(_context, exceptionVal);
JS_FreeValue(_context, res);
throw std::runtime_error("[" + _metadata.Name + "]\n" + details);
}
duk_pop(_context);
JS_FreeValue(_context, res);
}
void Plugin::StopBegin()
@@ -112,16 +113,16 @@ void Plugin::StopEnd()
_hasStarted = false;
}
void Plugin::ThrowIfStopping() const
{
if (IsStopping())
{
duk_error(_context, DUK_ERR_ERROR, "Plugin is stopping.");
}
}
void Plugin::Unload()
{
if (!_context)
{
throw std::runtime_error("Plugin is not loaded");
}
JS_FreeContext(_context);
_context = nullptr;
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105937, fixed in GCC13
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
@@ -139,34 +140,51 @@ void Plugin::LoadCodeFromFile()
_code = File::ReadAllText(_path);
}
static std::string TryGetString(const DukValue& value, const std::string& message)
std::string Plugin::TryGetString(const JSValue value, const char* property, const std::string& message) const
{
if (value.type() != DukValue::Type::STRING)
const JSValue res = JS_GetPropertyStr(_context, value, property);
if (!JS_IsString(res))
{
JS_FreeValue(_context, res);
throw std::runtime_error(message);
return value.as_string();
}
std::string str = JSToStdString(_context, res);
JS_FreeValue(_context, res);
return str;
}
PluginMetadata Plugin::GetMetadata(const DukValue& dukMetadata)
void Plugin::SetMetadata(const JSValue obj)
{
PluginMetadata metadata;
if (dukMetadata.type() == DukValue::Type::OBJECT)
PluginMetadata metadata{};
if (JS_IsObject(obj))
{
metadata.Name = TryGetString(dukMetadata["name"], "Plugin name not specified.");
metadata.Version = TryGetString(dukMetadata["version"], "Plugin version not specified.");
metadata.Type = ParsePluginType(TryGetString(dukMetadata["type"], "Plugin type not specified."));
metadata.Name = TryGetString(obj, "name", "Plugin name not specified.");
metadata.Version = TryGetString(obj, "version", "Plugin version not specified.");
metadata.Type = ParsePluginType(TryGetString(obj, "type", "Plugin type not specified."));
CheckForLicence(dukMetadata["licence"], metadata.Name);
auto dukMinApiVersion = dukMetadata["minApiVersion"];
if (dukMinApiVersion.type() == DukValue::Type::NUMBER)
const JSValue licence = JS_GetPropertyStr(_context, obj, "licence");
int64_t licenseLen = -1;
if (!JS_IsString(licence) || JS_GetLength(_context, licence, &licenseLen) == -1 || licenseLen == 0)
{
metadata.MinApiVersion = dukMinApiVersion.as_uint();
LOG_ERROR("Plugin %s does not specify a licence", _metadata.Name.c_str());
}
JS_FreeValue(_context, licence);
auto dukTargetApiVersion = dukMetadata["targetApiVersion"];
if (dukTargetApiVersion.type() == DukValue::Type::NUMBER)
const JSValue minApiVersion = JS_GetPropertyStr(_context, obj, "minApiVersion");
if (JS_IsNumber(minApiVersion))
{
metadata.TargetApiVersion = dukTargetApiVersion.as_uint();
int64_t val = -1;
JS_ToInt64(_context, &val, minApiVersion);
metadata.MinApiVersion = val;
}
JS_FreeValue(_context, minApiVersion);
const JSValue targetApiVersion = JS_GetPropertyStr(_context, obj, "targetApiVersion");
if (JS_IsNumber(targetApiVersion))
{
int64_t val = -1;
JS_ToInt64(_context, &val, targetApiVersion);
metadata.TargetApiVersion = val;
}
else
{
@@ -174,23 +192,25 @@ PluginMetadata Plugin::GetMetadata(const DukValue& dukMetadata)
u8"Plug-in “%s” does not specify a target API version or specifies it incorrectly. Emulating deprecated APIs.",
metadata.Name.c_str());
}
JS_FreeValue(_context, targetApiVersion);
auto dukAuthors = dukMetadata["authors"];
dukAuthors.push();
if (dukAuthors.is_array())
const JSValue authors = JS_GetPropertyStr(_context, obj, "authors");
if (JS_IsArray(authors))
{
auto elements = dukAuthors.as_array();
std::transform(elements.begin(), elements.end(), std::back_inserter(metadata.Authors), [](const DukValue& v) {
return v.as_string();
JSIterateArray(_context, authors, [&metadata](JSContext* ctx2, JSValue val) {
if (JS_IsString(val))
metadata.Authors.emplace_back(JSToStdString(ctx2, val));
});
}
else if (dukAuthors.type() == DukValue::Type::STRING)
else if (JS_IsString(authors))
{
metadata.Authors = { dukAuthors.as_string() };
metadata.Authors = { JSToStdString(_context, authors) };
}
metadata.Main = dukMetadata["main"];
JS_FreeValue(_context, authors);
metadata.Main = JSToCallback(_context, obj, "main");
}
return metadata;
_metadata = metadata;
}
PluginType Plugin::ParsePluginType(std::string_view type)
@@ -204,12 +224,6 @@ PluginType Plugin::ParsePluginType(std::string_view type)
throw std::invalid_argument("Unknown plugin type.");
}
void Plugin::CheckForLicence(const DukValue& dukLicence, std::string_view pluginName)
{
if (dukLicence.type() != DukValue::Type::STRING || dukLicence.as_string().empty())
LOG_ERROR("Plugin %s does not specify a licence", std::string(pluginName).c_str());
}
int32_t Plugin::GetTargetAPIVersion() const
{
if (_metadata.TargetApiVersion)
+16 -8
View File
@@ -11,9 +11,10 @@
#ifdef ENABLE_SCRIPTING
#include "Duktape.hpp"
#include "ScriptUtil.hpp"
#include <memory>
#include <optional>
#include <quickjs.h>
#include <string>
#include <string_view>
#include <vector>
@@ -49,13 +50,13 @@ namespace OpenRCT2::Scripting
PluginType Type{};
int32_t MinApiVersion{};
std::optional<int32_t> TargetApiVersion{};
DukValue Main;
JSCallback Main;
};
class Plugin
{
private:
duk_context* _context{};
JSContext* _context = nullptr;
std::string _path;
PluginMetadata _metadata{};
std::string _code;
@@ -63,12 +64,19 @@ namespace OpenRCT2::Scripting
bool _hasStarted{};
bool _isStopping{};
std::string TryGetString(JSValue value, const char* property, const std::string& message) const;
public:
std::string_view GetPath() const
{
return _path;
}
JSContext* GetContext() const
{
return _context;
}
bool HasPath() const
{
return !_path.empty();
@@ -79,6 +87,8 @@ namespace OpenRCT2::Scripting
return _metadata;
}
void SetMetadata(JSValue obj);
const std::string& GetCode() const
{
return _code;
@@ -102,7 +112,7 @@ namespace OpenRCT2::Scripting
int32_t GetTargetAPIVersion() const;
Plugin() = default;
Plugin(duk_context* context, std::string_view path);
explicit Plugin(std::string_view path);
Plugin(const Plugin&) = delete;
Plugin(Plugin&&) = delete;
@@ -112,7 +122,6 @@ namespace OpenRCT2::Scripting
void StopBegin();
void StopEnd();
void ThrowIfStopping() const;
void Unload();
bool IsTransient() const;
@@ -120,9 +129,8 @@ namespace OpenRCT2::Scripting
private:
void LoadCodeFromFile();
static PluginMetadata GetMetadata(const DukValue& dukMetadata);
static PluginType ParsePluginType(std::string_view type);
static void CheckForLicence(const DukValue& dukLicence, std::string_view pluginName);
static void CheckForLicence(JSValue dukLicence, std::string_view pluginName);
};
} // namespace OpenRCT2::Scripting
File diff suppressed because it is too large Load Diff
+112 -55
View File
@@ -13,8 +13,8 @@
#include "../actions/general/CustomAction.h"
#include "../core/FileWatcher.h"
#include "../core/Guard.hpp"
#include "../management/Finance.h"
#include "../world/Location.hpp"
#include "HookEngine.h"
#include "Plugin.h"
@@ -23,14 +23,12 @@
#include <memory>
#include <mutex>
#include <queue>
#include <quickjs.h>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
struct duk_hthread;
typedef struct duk_hthread duk_context;
namespace OpenRCT2::GameActions
{
class GameAction;
@@ -56,7 +54,7 @@ namespace OpenRCT2::Scripting
static constexpr int32_t kApiVersionNetworkIDs = 77;
#ifndef DISABLE_NETWORK
class ScSocketBase;
struct SocketDataBase;
#endif
class ScriptExecutionInfo
@@ -105,34 +103,13 @@ namespace OpenRCT2::Scripting
}
};
class DukContext
{
private:
duk_context* _context{};
public:
DukContext();
DukContext(DukContext&) = delete;
DukContext(DukContext&& src) noexcept
: _context(std::move(src._context))
{
src._context = {};
}
~DukContext();
operator duk_context*()
{
return _context;
}
};
using IntervalHandle = uint32_t;
struct ScriptInterval
{
std::shared_ptr<Plugin> Owner;
uint32_t Delay{};
int64_t LastTimestamp{};
DukValue Callback;
JSCallback Callback;
bool Repeat{};
bool Deleted{};
};
@@ -142,7 +119,8 @@ namespace OpenRCT2::Scripting
private:
InteractiveConsole& _console;
IPlatformEnvironment& _env;
DukContext _context;
static JSRuntime* _runtime;
JSContext* _replContext = nullptr;
bool _initialised{};
bool _hotReloadingInitialised{};
bool _transientPluginsEnabled{};
@@ -153,8 +131,8 @@ namespace OpenRCT2::Scripting
uint32_t _lastHotReloadCheckTick{};
HookEngine _hookEngine;
ScriptExecutionInfo _execInfo;
DukValue _sharedStorage;
DukValue _parkStorage;
JSValue _sharedStorage = JS_UNDEFINED;
JSValue _parkStorage = JS_UNDEFINED;
uint32_t _lastIntervalTimestamp{};
std::map<IntervalHandle, ScriptInterval> _intervals;
@@ -165,26 +143,37 @@ namespace OpenRCT2::Scripting
std::mutex _changedPluginFilesMutex;
std::vector<std::function<void(std::shared_ptr<Plugin>)>> _pluginStoppedSubscriptions;
struct ExtensionCallbacks
{
std::function<void(JSContext*)> newContext;
std::function<void()> unregister;
};
std::vector<ExtensionCallbacks> _extensions;
struct CustomActionInfo
{
std::shared_ptr<Plugin> Owner;
std::string Name;
DukValue Query;
DukValue Execute;
JSCallback Query;
JSCallback Execute;
};
std::unordered_map<std::string, CustomActionInfo> _customActions;
#ifndef DISABLE_NETWORK
std::list<std::shared_ptr<ScSocketBase>> _sockets;
std::vector<SocketDataBase*> _sockets;
#endif
void InitialiseContext(JSContext* ctx) const;
public:
ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env);
ScriptEngine(ScriptEngine&) = delete;
~ScriptEngine();
duk_context* GetContext()
JSContext* GetContext()
{
return _context;
return _replContext;
}
HookEngine& GetHookEngine()
{
@@ -194,11 +183,11 @@ namespace OpenRCT2::Scripting
{
return _execInfo;
}
DukValue GetSharedStorage()
JSValue GetSharedStorage()
{
return _sharedStorage;
}
DukValue GetParkStorage()
JSValue GetParkStorage()
{
return _parkStorage;
}
@@ -223,20 +212,21 @@ namespace OpenRCT2::Scripting
void ClearParkStorage();
std::string GetParkStorageAsJSON();
void SetParkStorageFromJSON(std::string_view value);
void SetParkStorageFromJSON(const std::string& value, const std::string& filename);
void Initialise();
JSContext* CreateContext() const;
void LoadTransientPlugins();
void UnloadTransientPlugins();
void StopUnloadRegisterAllPlugins();
void Tick();
std::future<void> Eval(const std::string& s);
DukValue ExecutePluginCall(
const std::shared_ptr<Plugin>& plugin, const DukValue& func, const std::vector<DukValue>& args,
bool isGameStateMutable);
DukValue ExecutePluginCall(
std::shared_ptr<Plugin> plugin, const DukValue& func, const DukValue& thisValue, const std::vector<DukValue>& args,
bool isGameStateMutable);
void ExecutePluginCall(
const std::shared_ptr<Plugin>& plugin, JSValue func, const std::vector<JSValue>& args, bool isGameStateMutable,
bool keepArgsAlive = false);
JSValue ExecutePluginCall(
const std::shared_ptr<Plugin>& plugin, JSValue func, JSValue thisValue, const std::vector<JSValue>& args,
bool isGameStateMutable, bool keepArgsAlive = false, bool keepRetValueAlive = false);
void LogPluginInfo(std::string_view message);
void LogPluginInfo(const std::shared_ptr<Plugin>& plugin, std::string_view message);
@@ -246,32 +236,43 @@ namespace OpenRCT2::Scripting
_pluginStoppedSubscriptions.push_back(callback);
}
void RegisterExtension(std::function<void(JSContext*)> newContext, std::function<void()> unregister)
{
_extensions.emplace_back(newContext, unregister);
newContext(_replContext);
}
void AddNetworkPlugin(std::string_view code);
void RemoveNetworkPlugins();
[[nodiscard]] GameActions::Result QueryOrExecuteCustomGameAction(
const GameActions::CustomAction& action, bool isExecute);
bool RegisterCustomAction(
const std::shared_ptr<Plugin>& plugin, std::string_view action, const DukValue& query, const DukValue& execute);
const std::shared_ptr<Plugin>& plugin, std::string_view action, const JSCallback& query, const JSCallback& execute);
void RunGameActionHooks(const GameActions::GameAction& action, GameActions::Result& result, bool isExecute);
[[nodiscard]] std::unique_ptr<GameActions::GameAction> CreateGameAction(
const std::string& actionid, const DukValue& args, const std::string& pluginName);
[[nodiscard]] DukValue GameActionResultToDuk(const GameActions::GameAction& action, const GameActions::Result& result);
[[nodiscard]] std::pair<std::unique_ptr<GameActions::GameAction>, bool> CreateGameAction(
JSContext* ctx, const std::string& actionid, JSValue args, const std::string& pluginName);
[[nodiscard]] JSValue GameActionResultToJS(
JSContext* ctx, const GameActions::GameAction& action, const GameActions::Result& result);
void SaveSharedStorage();
IntervalHandle AddInterval(const std::shared_ptr<Plugin>& plugin, int32_t delay, bool repeat, DukValue&& callback);
IntervalHandle AddInterval(
const std::shared_ptr<Plugin>& plugin, int32_t delay, bool repeat, const JSCallback& callback);
void RemoveInterval(const std::shared_ptr<Plugin>& plugin, IntervalHandle handle);
static std::string_view ExpenditureTypeToString(ExpenditureType expenditureType);
static ExpenditureType StringToExpenditureType(std::string_view expenditureType);
#ifndef DISABLE_NETWORK
void AddSocket(const std::shared_ptr<ScSocketBase>& socket);
void AddSocket(SocketDataBase* data);
void RemoveSocket(SocketDataBase* data);
#endif
private:
void RegisterConstants();
static void RegisterClasses(JSContext* ctx);
static void UnregisterClasses();
static void RegisterConstants(JSContext* ctx);
void RefreshPlugins();
std::vector<std::string> GetPluginFiles() const;
void UnregisterPlugin(std::string_view path);
@@ -283,7 +284,7 @@ namespace OpenRCT2::Scripting
void LoadPlugin(std::shared_ptr<Plugin>& plugin);
void UnloadPlugin(std::shared_ptr<Plugin>& plugin);
void StartPlugin(std::shared_ptr<Plugin> plugin);
void StopPlugin(std::shared_ptr<Plugin> plugin);
void StopPlugin(std::shared_ptr<Plugin> plugin, bool unregistering = false);
void ReloadPlugin(std::shared_ptr<Plugin> plugin);
static bool ShouldLoadScript(std::string_view path);
bool ShouldStartPlugin(const std::shared_ptr<Plugin>& plugin);
@@ -292,7 +293,7 @@ namespace OpenRCT2::Scripting
void AutoReloadPlugins();
void ProcessREPL();
void RemoveCustomGameActions(const std::shared_ptr<Plugin>& plugin);
[[nodiscard]] GameActions::Result DukToGameActionResult(const DukValue& d);
[[nodiscard]] static GameActions::Result JSToGameActionResult(JSContext* ctx, JSValue d);
void InitSharedStorage();
void LoadSharedStorage();
@@ -306,10 +307,66 @@ namespace OpenRCT2::Scripting
};
bool IsGameStateMutable();
void ThrowIfGameStateNotMutable();
int32_t GetTargetAPIVersion();
std::string Stringify(const DukValue& value);
std::string Stringify(JSContext* context, JSValue value);
class ScBase
{
private:
JSClassID classId = JS_INVALID_CLASS_ID;
public:
void Unregister()
{
classId = JS_INVALID_CLASS_ID;
}
protected:
[[nodiscard]] JSValue MakeWithOpaque(JSContext* ctx, std::span<const JSCFunctionListEntry> classFuncs, void* opaque)
{
JSValue obj = JS_NewObjectClass(ctx, classId);
if (JS_IsException(obj))
throw std::runtime_error("Failed to create new object for class.");
JS_SetOpaque(obj, opaque);
// Note: Usually one would set a class prototype rather than setting the functions as properties on every creation.
// However, that causes the attached functions to not be "own properties" which make them a little less
// visible to the user, and also does not match the previous behaviour with the DukTape engine.
JS_SetPropertyFunctionList(ctx, obj, classFuncs.data(), static_cast<int>(classFuncs.size()));
return obj;
}
template<typename T>
[[nodiscard]] T GetOpaque(JSValue obj) const
{
return static_cast<T>(JS_GetOpaque(obj, classId));
}
void RegisterBase(JSContext* ctx, const JSClassDef& classDef)
{
Guard::Assert(classId == JS_INVALID_CLASS_ID);
// Note: Technically JS_NewClassID is meant to be called once during the lifetime of the program
// whereas JS_NewClass is meant to be called for each runtime.
// If we ever have any more runtimes, this flow would be wrong.
// More runtimes would also require refactoring the way values are passed to the JS code when
// calling callbacks.
JSRuntime* rt = JS_GetRuntime(ctx);
JS_NewClassID(rt, &classId);
JS_NewClass(rt, classId, &classDef);
JS_SetClassProto(ctx, classId, JS_NewObject(ctx));
}
void RegisterBaseStr(JSContext* ctx, const char* className)
{
RegisterBase(ctx, { className, nullptr, nullptr, nullptr, nullptr });
}
void RegisterBaseStr(JSContext* ctx, const char* className, JSClassFinalizer* finalizer)
{
RegisterBase(ctx, { className, finalizer, nullptr, nullptr, nullptr });
}
};
} // namespace OpenRCT2::Scripting
+614
View File
@@ -0,0 +1,614 @@
/*****************************************************************************
* Copyright (c) 2014-2025 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 "../world/Location.hpp"
#include <functional>
#include <optional>
#include <quickjs.h>
#include <string>
namespace OpenRCT2::Scripting
{
struct JSCallback
{
JSContext* context = nullptr;
JSValue callback = JS_UNDEFINED;
JSCallback() {};
JSCallback(JSContext* ctx, JSValue func)
: context(ctx)
, callback(ctx && JS_IsFunction(ctx, func) ? JS_DupValue(ctx, func) : JS_UNDEFINED)
{
}
JSCallback(const JSCallback& other)
: context(other.context)
, callback(
other.context && JS_IsFunction(other.context, other.callback) ? JS_DupValue(other.context, other.callback)
: JS_UNDEFINED)
{
}
JSCallback(JSCallback&& other) noexcept
: context(other.context)
, callback(other.callback)
{
other.context = nullptr;
other.callback = JS_UNDEFINED;
}
JSCallback& operator=(const JSCallback& other)
{
if (this != &other)
{
if (context)
{
JS_FreeValue(context, callback);
}
context = other.context;
callback = JS_DupValue(other.context, other.callback);
}
return *this;
}
JSCallback& operator=(JSCallback&& other) noexcept
{
if (this != &other)
{
if (context)
{
JS_FreeValue(context, callback);
}
context = other.context;
callback = other.callback;
other.context = nullptr;
other.callback = JS_UNDEFINED;
}
return *this;
}
bool IsValid() const
{
return context && JS_IsFunction(context, callback);
}
~JSCallback() noexcept
{
if (context)
{
JS_FreeValue(context, callback);
}
}
};
inline JSCallback JSToCallback(JSContext* ctx, JSValue obj, const char* name)
{
JSValue property = JS_GetPropertyStr(ctx, obj, name);
JSCallback out(ctx, property);
JS_FreeValue(ctx, property);
return out;
}
inline std::string JSToStdString(JSContext* ctx, JSValue obj)
{
size_t len;
if (const char* buf = JS_ToCStringLen(ctx, &len, obj))
{
std::string str(buf, len);
JS_FreeCString(ctx, buf);
return str;
}
return {};
}
inline std::string JSToStdString(JSContext* ctx, JSValue obj, const char* property)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
std::string output = JSToStdString(ctx, val);
JS_FreeValue(ctx, val);
return output;
}
inline JSValue JSFromStdString(JSContext* ctx, const std::string_view str)
{
return JS_NewStringLen(ctx, str.data(), str.length());
}
inline std::optional<std::string> JSToOptionalStdString(JSContext* ctx, JSValue obj, const char* property)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
std::optional<std::string> output = std::nullopt;
if (JS_IsString(val))
{
output = JSToStdString(ctx, val);
}
JS_FreeValue(ctx, val);
return output;
}
inline std::optional<int32_t> JSToOptionalInt(JSContext* ctx, JSValue obj, const char* property)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
std::optional<int32_t> output = std::nullopt;
if (JS_IsNumber(val))
{
int32_t intVal = -1;
if (JS_ToInt32(ctx, &intVal, val) >= 0)
{
output = std::make_optional(intVal);
}
}
JS_FreeValue(ctx, val);
return output;
}
inline std::optional<int64_t> JSToOptionalInt64(JSContext* ctx, JSValue obj, const char* property)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
std::optional<int64_t> output = std::nullopt;
if (JS_IsNumber(val))
{
int64_t intVal = -1;
if (JS_ToInt64(ctx, &intVal, val) >= 0)
{
output = std::make_optional(intVal);
}
}
JS_FreeValue(ctx, val);
return output;
}
inline std::optional<uint32_t> JSToOptionalUint(JSContext* ctx, JSValue obj, const char* property)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
std::optional<uint32_t> output = std::nullopt;
if (JS_IsNumber(val))
{
uint32_t uintVal = 0;
if (JS_ToUint32(ctx, &uintVal, val) >= 0)
{
output = std::make_optional(uintVal);
}
}
JS_FreeValue(ctx, val);
return output;
}
inline std::optional<bool> JSToOptionalBool(JSContext* ctx, JSValue obj, const char* property)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
std::optional<bool> output = std::nullopt;
if (JS_IsBool(val))
{
const int result = JS_ToBool(ctx, val);
if (result != -1)
{
output = std::make_optional(static_cast<bool>(result));
}
}
JS_FreeValue(ctx, val);
return output;
}
inline bool AsOrDefault(JSContext* ctx, JSValue obj, const char* property, bool def)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
bool output;
if (JS_IsBool(val))
{
int result = JS_ToBool(ctx, val);
output = result >= 0 ? result : def;
}
else
{
output = def;
}
JS_FreeValue(ctx, val);
return output;
}
inline int32_t AsOrDefault(JSContext* ctx, JSValue obj, const char* property, int32_t def)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
int32_t output;
if (!JS_IsNumber(val))
{
output = def;
}
else if (JS_ToInt32(ctx, &output, val) < 0)
{
output = def;
}
JS_FreeValue(ctx, val);
return output;
}
inline uint32_t AsOrDefault(JSContext* ctx, JSValue obj, const char* property, uint32_t def)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
uint32_t output;
if (!JS_IsNumber(val))
{
output = def;
}
else if (JS_ToUint32(ctx, &output, val) < 0)
{
output = def;
}
JS_FreeValue(ctx, val);
return output;
}
inline int64_t AsOrDefault(JSContext* ctx, JSValue obj, const char* property, int64_t def)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
int64_t output;
if (!JS_IsNumber(val))
{
output = def;
}
else if (JS_ToInt64(ctx, &output, val) < 0)
{
output = def;
}
JS_FreeValue(ctx, val);
return output;
}
// Note the default parameter needs to be const char*
// If you use a type like string_view, calls can instead resolve to the boolean version of the function.
inline std::string AsOrDefault(JSContext* ctx, JSValue obj, const char* property, const char* def)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
std::string output;
if (!JS_IsString(val))
{
output = std::string(def);
}
else
{
output = JSToStdString(ctx, val);
}
JS_FreeValue(ctx, val);
return output;
}
inline void JSIterateArray(JSContext* ctx, JSValue val, const std::function<void(JSContext*, JSValue)>& callback)
{
if (JS_IsArray(val))
{
int64_t arrayLen = -1;
JS_GetLength(ctx, val, &arrayLen);
for (int64_t i = 0; i < arrayLen; i++)
{
JSValue elem = JS_GetPropertyInt64(ctx, val, i);
callback(ctx, elem);
JS_FreeValue(ctx, elem);
}
}
}
inline void JSIterateArray(
JSContext* ctx, JSValue obj, const char* property, const std::function<void(JSContext*, JSValue)>& callback)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
JSIterateArray(ctx, val, callback);
JS_FreeValue(ctx, val);
}
inline int32_t JSToInt(JSContext* ctx, JSValue val)
{
int32_t output = -1;
if (JS_IsNumber(val))
{
JS_ToInt32(ctx, &output, val);
}
return output;
}
inline int32_t JSToInt(JSContext* ctx, JSValue obj, const char* property)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
int32_t output = JSToInt(ctx, val);
JS_FreeValue(ctx, val);
return output;
}
inline int64_t JSToInt64(JSContext* ctx, JSValue val)
{
int64_t output = -1;
if (JS_IsNumber(val))
{
JS_ToInt64(ctx, &output, val);
}
return output;
}
inline int64_t JSToInt64(JSContext* ctx, JSValue obj, const char* property)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
int64_t output = JSToInt64(ctx, val);
JS_FreeValue(ctx, val);
return output;
}
inline uint32_t JSToUint(JSContext* ctx, JSValue val)
{
uint32_t output = 0;
if (JS_IsNumber(val))
{
JS_ToUint32(ctx, &output, val);
}
return output;
}
inline uint32_t JSToUint(JSContext* ctx, JSValue obj, const char* property)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
uint32_t output = JSToUint(ctx, val);
JS_FreeValue(ctx, val);
return output;
}
inline CoordsXY JSToCoordsXY(JSContext* ctx, JSValue obj)
{
return { AsOrDefault(ctx, obj, "x", 0), AsOrDefault(ctx, obj, "y", 0) };
}
inline CoordsXY JSToCoordXY(JSContext* ctx, JSValue obj, const char* property)
{
JSValue val = JS_GetPropertyStr(ctx, obj, property);
CoordsXY output = JSToCoordsXY(ctx, val);
JS_FreeValue(ctx, val);
return output;
}
inline CoordsXYZ JSToCoordsXYZ(JSContext* ctx, JSValue obj)
{
CoordsXYZ result;
if (JS_IsObject(obj))
{
result.x = AsOrDefault(ctx, obj, "x", 0);
result.y = AsOrDefault(ctx, obj, "y", 0);
result.z = AsOrDefault(ctx, obj, "z", 0);
}
else
{
result.SetNull();
}
return result;
}
inline CoordsXYZD JSToCoordsXYZD(JSContext* ctx, JSValue obj)
{
CoordsXYZD result;
if (JS_IsObject(obj))
{
result = CoordsXYZD(JSToCoordsXYZ(ctx, obj), AsOrDefault(ctx, obj, "direction", 0));
}
else
{
result.SetNull();
}
return result;
}
inline JSValue ToJSValue(JSContext* ctx, uint8_t val)
{
return JS_NewInt32(ctx, val);
}
inline JSValue ToJSValue(JSContext* ctx, const CoordsXY& coords)
{
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, coords.x));
JS_SetPropertyStr(ctx, obj, "y", JS_NewInt32(ctx, coords.y));
return obj;
}
inline JSValue ToJSValue(JSContext* ctx, const TileCoordsXY& coords)
{
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, coords.x));
JS_SetPropertyStr(ctx, obj, "y", JS_NewInt32(ctx, coords.y));
return obj;
}
inline JSValue ToJSValue(JSContext* ctx, const ScreenCoordsXY& coords)
{
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, coords.x));
JS_SetPropertyStr(ctx, obj, "y", JS_NewInt32(ctx, coords.y));
return obj;
}
inline JSValue ToJSValue(JSContext* ctx, const ScreenSize& size)
{
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "width", JS_NewInt32(ctx, size.width));
JS_SetPropertyStr(ctx, obj, "height", JS_NewInt32(ctx, size.height));
return obj;
}
inline JSValue ToJSValue(JSContext* ctx, const CoordsXYZ& value)
{
if (value.IsNull())
{
return JS_NULL;
}
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, value.x));
JS_SetPropertyStr(ctx, obj, "y", JS_NewInt32(ctx, value.y));
JS_SetPropertyStr(ctx, obj, "z", JS_NewInt32(ctx, value.z));
return obj;
}
inline JSValue ToJSValue(JSContext* ctx, const CoordsXYZD& value)
{
if (value.IsNull())
{
return JS_NULL;
}
JSValue obj = ToJSValue(ctx, static_cast<const CoordsXYZ&>(value));
JS_SetPropertyStr(ctx, obj, "direction", JS_NewUint32(ctx, value.direction));
return obj;
}
template<typename T>
JSValue ToJSValue(JSContext* ctx, const std::optional<T>& value)
{
return value ? ToJSValue(ctx, *value) : JS_NULL;
}
#define JS_THROW_IF_GAME_STATE_NOT_MUTABLE() \
if (!Scripting::IsGameStateMutable()) \
{ \
JS_ThrowPlainError(ctx, "Game state is not mutable in this context."); \
return JS_EXCEPTION; \
}
#define JS_UNPACK_INT32(var, ctx, val) \
int32_t var; \
if (!JS_IsNumber(val)) \
{ \
JS_ThrowTypeError(ctx, "Expected number"); \
return JS_EXCEPTION; \
} \
if (JS_ToInt32(ctx, &var, val) < 0) \
{ \
return JS_EXCEPTION; \
}
#define JS_UNPACK_INT64(var, ctx, val) \
int64_t var; \
if (!JS_IsNumber(val)) \
{ \
JS_ThrowTypeError(ctx, "Expected number"); \
return JS_EXCEPTION; \
} \
if (JS_ToInt64(ctx, &var, val) < 0) \
{ \
return JS_EXCEPTION; \
}
#define JS_UNPACK_UINT32(var, ctx, val) \
uint32_t var; \
if (!JS_IsNumber(val)) \
{ \
JS_ThrowTypeError(ctx, "Expected number"); \
return JS_EXCEPTION; \
} \
if (JS_ToUint32(ctx, &var, val) < 0) \
{ \
return JS_EXCEPTION; \
}
#define JS_UNPACK_MONEY64(var, ctx, val) \
money64 var; \
if (!JS_IsNumber(val)) \
{ \
JS_ThrowTypeError(ctx, "Expected number"); \
return JS_EXCEPTION; \
} \
if (JS_ToInt64(ctx, &var, val) < 0) \
{ \
return JS_EXCEPTION; \
}
#define JS_UNPACK_STR(var, ctx, val) \
std::string var; \
if (!JS_IsString(val)) \
{ \
JS_ThrowTypeError(ctx, "Expected string"); \
return JS_EXCEPTION; \
} \
{ \
size_t len; \
if (const char* buf = JS_ToCStringLen(ctx, &len, val)) \
{ \
var = std::string(buf, len); \
JS_FreeCString(ctx, buf); \
} \
else \
{ \
return JS_EXCEPTION; \
} \
}
#define JS_UNPACK_BOOL(var, ctx, val) \
bool var; \
if (!JS_IsBool(val)) \
{ \
JS_ThrowTypeError(ctx, "Expected boolean"); \
return JS_EXCEPTION; \
} \
{ \
const int result = JS_ToBool(ctx, val); \
if (result == -1) \
{ \
return JS_EXCEPTION; \
} \
var = result; \
}
#define JS_UNPACK_ARRAY(var, ctx, val) \
JSValue var = val; \
if (!JS_IsArray(val)) \
{ \
JS_ThrowTypeError(ctx, "Expected array"); \
return JS_EXCEPTION; \
}
#define JS_UNPACK_CALLBACK(var, ctx, val) \
JSCallback var(ctx, val); \
if (!var.IsValid()) \
{ \
JS_ThrowTypeError(ctx, "Expected function"); \
return JS_EXCEPTION; \
}
#define JS_UNPACK_OBJECT(var, ctx, val) \
JSValue var = val; \
if (!JS_IsObject(var)) \
{ \
JS_ThrowTypeError(ctx, "Expected object"); \
return JS_EXCEPTION; \
}
#define JS_UNPACK_COORDSXY(var, ctx, val) \
CoordsXY var; \
if (!JS_IsObject(var)) \
{ \
JS_ThrowTypeError(ctx, "Expected CoordsXY"); \
return JS_EXCEPTION; \
} \
{ \
auto x = JSToOptionalInt(ctx, position, "x"); \
auto y = JSToOptionalInt(ctx, position, "y"); \
if (x.has_value() && y.has_value()) \
{ \
var = CoordsXY(x.value(), y.value()); \
} \
else \
{ \
JS_ThrowTypeError(ctx, "Expected CoordsXY"); \
return JS_EXCEPTION; \
} \
}
} // namespace OpenRCT2::Scripting
#endif
@@ -15,40 +15,43 @@
namespace OpenRCT2::Scripting
{
ScBalloon::ScBalloon(EntityId Id)
: ScEntity(Id)
JSValue ScBalloon::New(JSContext* ctx, EntityId entityId)
{
JSValue obj = gScEntity.New(ctx, entityId);
AddFuncs(ctx, obj);
return obj;
}
void ScBalloon::Register(duk_context* ctx)
void ScBalloon::AddFuncs(JSContext* ctx, JSValue obj)
{
dukglue_set_base_class<ScEntity, ScBalloon>(ctx);
dukglue_register_property(ctx, &ScBalloon::colour_get, &ScBalloon::colour_set, "colour");
static constexpr JSCFunctionListEntry funcs[] = { JS_CGETSET_DEF(
"colour", &ScBalloon::colour_get, &ScBalloon::colour_set) };
JS_SetPropertyFunctionList(ctx, obj, funcs, std::size(funcs));
}
Balloon* ScBalloon::GetBalloon() const
Balloon* ScBalloon::GetBalloon(JSValue thisVal)
{
return getGameState().entities.GetEntity<Balloon>(_id);
auto id = GetEntityId(thisVal);
return getGameState().entities.GetEntity<Balloon>(id);
}
uint8_t ScBalloon::colour_get() const
JSValue ScBalloon::colour_get(JSContext* ctx, JSValue thisVal)
{
auto balloon = GetBalloon();
if (balloon != nullptr)
{
return EnumValue(balloon->colour);
}
return 0;
auto balloon = GetBalloon(thisVal);
return JS_NewUint32(ctx, balloon == nullptr ? 0 : EnumValue(balloon->colour));
}
void ScBalloon::colour_set(uint8_t value)
JSValue ScBalloon::colour_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
auto balloon = GetBalloon();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto balloon = GetBalloon(thisVal);
if (balloon != nullptr)
{
balloon->colour = static_cast<Drawing::Colour>(value);
balloon->Invalidate();
}
return JS_UNDEFINED;
}
} // namespace OpenRCT2::Scripting
@@ -20,19 +20,17 @@ namespace OpenRCT2
namespace OpenRCT2::Scripting
{
class ScBalloon : public ScEntity
class ScBalloon final : public ScEntity
{
public:
ScBalloon(EntityId Id);
static void Register(duk_context* ctx);
static JSValue New(JSContext* ctx, EntityId entityId);
private:
Balloon* GetBalloon() const;
static void AddFuncs(JSContext* ctx, JSValue obj);
static Balloon* GetBalloon(JSValue thisVal);
uint8_t colour_get() const;
void colour_set(uint8_t);
static JSValue colour_get(JSContext* ctx, JSValue thisVal);
static JSValue colour_set(JSContext* ctx, JSValue thisVal, JSValue value);
};
} // namespace OpenRCT2::Scripting
#endif
@@ -0,0 +1,234 @@
/*****************************************************************************
* Copyright (c) 2014-2025 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.
*****************************************************************************/
#ifdef ENABLE_SCRIPTING
#include "ScEntity.hpp"
#include "../../../entity/Staff.h"
#include "ScBalloon.hpp"
#include "ScGuest.hpp"
#include "ScLitter.hpp"
#include "ScMoneyEffect.hpp"
#include "ScParticle.hpp"
#include "ScPeep.hpp"
#include "ScStaff.hpp"
#include "ScVehicle.hpp"
namespace OpenRCT2::Scripting
{
static inline std::string EntityTypeToString(const EntityBase* entity)
{
const auto targetApiVersion = GetTargetAPIVersion();
if (entity != nullptr)
{
switch (entity->Type)
{
case EntityType::vehicle:
return "car";
case EntityType::guest:
if (targetApiVersion <= kApiVersionPeepDeprecation)
return "peep";
return "guest";
case EntityType::staff:
if (targetApiVersion <= kApiVersionPeepDeprecation)
return "peep";
return "staff";
case EntityType::steamParticle:
return "steam_particle";
case EntityType::moneyEffect:
return "money_effect";
case EntityType::crashedVehicleParticle:
return "crashed_vehicle_particle";
case EntityType::explosionCloud:
return "explosion_cloud";
case EntityType::crashSplash:
return "crash_splash";
case EntityType::explosionFlare:
return "explosion_flare";
case EntityType::balloon:
return "balloon";
case EntityType::duck:
return "duck";
case EntityType::jumpingFountain:
return "jumping_fountain";
case EntityType::litter:
return "litter";
case EntityType::null:
return "unknown";
default:
break;
}
}
return "unknown";
}
using OpaqueEntityData = struct
{
EntityId id;
};
JSValue ScEntity::id_get(JSContext* ctx, JSValue thisVal)
{
auto entity = GetEntity(thisVal);
if (entity == nullptr)
return JS_UNDEFINED;
return JS_NewInt32(ctx, entity->Id.ToUnderlying());
}
JSValue ScEntity::type_get(JSContext* ctx, JSValue thisVal)
{
auto entity = GetEntity(thisVal);
auto type = EntityTypeToString(entity);
return JSFromStdString(ctx, type);
}
// x getter and setter
JSValue ScEntity::x_get(JSContext* ctx, JSValue thisVal)
{
auto entity = GetEntity(thisVal);
return JS_NewInt32(ctx, entity != nullptr ? entity->x : 0);
}
JSValue ScEntity::x_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
JS_UNPACK_INT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto entity = GetEntity(thisVal);
if (entity != nullptr)
{
entity->MoveTo({ value, entity->y, entity->z });
}
return JS_UNDEFINED;
}
// y getter and setter
JSValue ScEntity::y_get(JSContext* ctx, JSValue thisVal)
{
auto entity = GetEntity(thisVal);
return JS_NewInt32(ctx, entity != nullptr ? entity->y : 0);
}
JSValue ScEntity::y_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
JS_UNPACK_INT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto entity = GetEntity(thisVal);
if (entity != nullptr)
{
entity->MoveTo({ entity->x, value, entity->z });
}
return JS_UNDEFINED;
}
// z getter and setter
JSValue ScEntity::z_get(JSContext* ctx, JSValue thisVal)
{
auto entity = GetEntity(thisVal);
return JS_NewInt32(ctx, entity != nullptr ? entity->z : 0);
}
JSValue ScEntity::z_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
JS_UNPACK_INT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto entity = GetEntity(thisVal);
if (entity != nullptr)
{
entity->MoveTo({ entity->x, entity->y, value });
}
return JS_UNDEFINED;
}
JSValue ScEntity::remove(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto entity = GetEntity(thisVal);
if (entity != nullptr)
{
entity->Invalidate();
switch (entity->Type)
{
case EntityType::vehicle:
JS_ThrowPlainError(ctx, "Removing a vehicle is currently unsupported.");
return JS_EXCEPTION;
case EntityType::guest:
case EntityType::staff:
{
auto peep = entity->As<Peep>();
// We can't remove a single peep from a ride at the moment as this can cause complications with the
// vehicle car having an unsupported peep capacity.
if (peep == nullptr || peep->State == PeepState::onRide || peep->State == PeepState::enteringRide)
{
JS_ThrowPlainError(ctx, "Removing a peep that is on a ride is currently unsupported.");
return JS_EXCEPTION;
}
peep->Remove();
break;
}
case EntityType::steamParticle:
case EntityType::moneyEffect:
case EntityType::crashedVehicleParticle:
case EntityType::explosionCloud:
case EntityType::crashSplash:
case EntityType::explosionFlare:
case EntityType::jumpingFountain:
case EntityType::balloon:
case EntityType::duck:
case EntityType::litter:
getGameState().entities.EntityRemove(entity);
break;
case EntityType::null:
break;
default:
break;
}
}
return JS_UNDEFINED;
}
EntityId ScEntity::GetEntityId(JSValue thisVal)
{
return gScEntity.GetOpaque<OpaqueEntityData*>(thisVal)->id;
}
EntityBase* ScEntity::GetEntity(JSValue thisVal)
{
auto id = GetEntityId(thisVal);
return OpenRCT2::getGameState().entities.GetEntity(id);
}
JSValue ScEntity::NewInstance(JSContext* ctx, EntityId entityId)
{
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("id", &ScEntity::id_get, nullptr), JS_CGETSET_DEF("type", &ScEntity::type_get, nullptr),
JS_CGETSET_DEF("x", &ScEntity::x_get, &ScEntity::x_set), JS_CGETSET_DEF("y", &ScEntity::y_get, &ScEntity::y_set),
JS_CGETSET_DEF("z", &ScEntity::z_get, &ScEntity::z_set), JS_CFUNC_DEF("remove", 0, &ScEntity::remove)
};
return MakeWithOpaque(ctx, funcs, new OpaqueEntityData{ entityId });
}
// this one exists as a hack to make a template work in ScMap
JSValue ScEntity::New(JSContext* ctx, EntityId entityId)
{
return gScEntity.NewInstance(ctx, entityId);
}
void ScEntity::Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Entity", Finalize);
}
void ScEntity::Finalize(JSRuntime* rt, JSValue thisVal)
{
OpaqueEntityData* data = gScEntity.GetOpaque<OpaqueEntityData*>(thisVal);
if (data)
delete data;
}
} // namespace OpenRCT2::Scripting
#endif
@@ -15,7 +15,6 @@
#include "../../../GameState.h"
#include "../../../entity/EntityRegistry.h"
#include "../../../entity/Peep.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
#include <string_view>
@@ -23,188 +22,41 @@
namespace OpenRCT2::Scripting
{
class ScEntity
class ScEntity;
extern ScEntity gScEntity;
class ScEntity : public ScBase
{
protected:
EntityId _id{ EntityId::GetNull() };
public:
ScEntity(EntityId id)
: _id(id)
{
}
private:
DukValue id_get() const
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto entity = GetEntity();
if (entity == nullptr)
return ToDuk(ctx, nullptr);
return ToDuk(ctx, entity->Id.ToUnderlying());
}
std::string type_get() const
{
const auto targetApiVersion = GetTargetAPIVersion();
auto entity = GetEntity();
if (entity != nullptr)
{
switch (entity->Type)
{
case EntityType::vehicle:
return "car";
case EntityType::guest:
if (targetApiVersion <= kApiVersionPeepDeprecation)
return "peep";
return "guest";
case EntityType::staff:
if (targetApiVersion <= kApiVersionPeepDeprecation)
return "peep";
return "staff";
case EntityType::steamParticle:
return "steam_particle";
case EntityType::moneyEffect:
return "money_effect";
case EntityType::crashedVehicleParticle:
return "crashed_vehicle_particle";
case EntityType::explosionCloud:
return "explosion_cloud";
case EntityType::crashSplash:
return "crash_splash";
case EntityType::explosionFlare:
return "explosion_flare";
case EntityType::balloon:
return "balloon";
case EntityType::duck:
return "duck";
case EntityType::jumpingFountain:
return "jumping_fountain";
case EntityType::litter:
return "litter";
case EntityType::null:
return "unknown";
default:
break;
}
}
return "unknown";
}
static JSValue id_get(JSContext* ctx, JSValue thisVal);
static JSValue type_get(JSContext* ctx, JSValue thisVal);
// x getter and setter
int32_t x_get() const
{
auto entity = GetEntity();
return entity != nullptr ? entity->x : 0;
}
void x_set(int32_t value)
{
ThrowIfGameStateNotMutable();
auto entity = GetEntity();
if (entity != nullptr)
{
entity->MoveTo({ value, entity->y, entity->z });
}
}
static JSValue x_get(JSContext* ctx, JSValue thisVal);
static JSValue x_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
// y getter and setter
int32_t y_get() const
{
auto entity = GetEntity();
return entity != nullptr ? entity->y : 0;
}
void y_set(int32_t value)
{
ThrowIfGameStateNotMutable();
auto entity = GetEntity();
if (entity != nullptr)
{
entity->MoveTo({ entity->x, value, entity->z });
}
}
static JSValue y_get(JSContext* ctx, JSValue thisVal);
static JSValue y_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
// z getter and setter
int16_t z_get() const
{
auto entity = GetEntity();
return entity != nullptr ? entity->z : 0;
}
void z_set(int16_t value)
{
ThrowIfGameStateNotMutable();
auto entity = GetEntity();
if (entity != nullptr)
{
entity->MoveTo({ entity->x, entity->y, value });
}
}
static JSValue z_get(JSContext* ctx, JSValue thisVal);
static JSValue z_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
void remove()
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto entity = GetEntity();
if (entity != nullptr)
{
entity->Invalidate();
switch (entity->Type)
{
case EntityType::vehicle:
duk_error(ctx, DUK_ERR_ERROR, "Removing a vehicle is currently unsupported.");
break;
case EntityType::guest:
case EntityType::staff:
{
auto peep = entity->As<Peep>();
// We can't remove a single peep from a ride at the moment as this can cause complications with the
// vehicle car having an unsupported peep capacity.
if (peep == nullptr || peep->State == PeepState::onRide || peep->State == PeepState::enteringRide)
{
duk_error(ctx, DUK_ERR_ERROR, "Removing a peep that is on a ride is currently unsupported.");
}
else
{
peep->Remove();
}
break;
}
case EntityType::steamParticle:
case EntityType::moneyEffect:
case EntityType::crashedVehicleParticle:
case EntityType::explosionCloud:
case EntityType::crashSplash:
case EntityType::explosionFlare:
case EntityType::jumpingFountain:
case EntityType::balloon:
case EntityType::duck:
case EntityType::litter:
getGameState().entities.EntityRemove(entity);
break;
case EntityType::null:
break;
default:
break;
}
}
}
static JSValue remove(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
EntityBase* GetEntity() const
{
return getGameState().entities.GetEntity(_id);
}
protected:
static EntityId GetEntityId(JSValue thisVal);
static EntityBase* GetEntity(JSValue thisVal);
public:
static void Register(duk_context* ctx)
{
dukglue_register_property(ctx, &ScEntity::id_get, nullptr, "id");
dukglue_register_property(ctx, &ScEntity::type_get, nullptr, "type");
dukglue_register_property(ctx, &ScEntity::x_get, &ScEntity::x_set, "x");
dukglue_register_property(ctx, &ScEntity::y_get, &ScEntity::y_set, "y");
dukglue_register_property(ctx, &ScEntity::z_get, &ScEntity::z_set, "z");
dukglue_register_method(ctx, &ScEntity::remove, "remove");
}
JSValue NewInstance(JSContext* ctx, EntityId entityId);
static JSValue New(JSContext* ctx, EntityId entityId);
void Register(JSContext* ctx);
private:
static void Finalize(JSRuntime* rt, JSValue thisVal);
};
} // namespace OpenRCT2::Scripting
File diff suppressed because it is too large Load Diff
@@ -22,7 +22,7 @@ namespace OpenRCT2
namespace OpenRCT2::Scripting
{
static const DukEnumMap<ShopItem> ShopItemMap(
static const EnumMap<ShopItem> ShopItemMap(
{
{ "beef_noodles", ShopItem::beefNoodles },
{ "burger", ShopItem::burger },
@@ -79,7 +79,7 @@ namespace OpenRCT2::Scripting
// guest cannot carry), 6 is subtracted from the value.
static_assert((EnumValue(ShopItem::count) - 6) == 50, "ShopItem::count changed, update scripting binding!");
static const DukEnumMap<uint32_t> VoucherTypeMap(
static const EnumMap<uint32_t> VoucherTypeMap(
{
{ "entry_free", VOUCHER_TYPE_PARK_ENTRY_FREE },
{ "entry_half_price", VOUCHER_TYPE_PARK_ENTRY_HALF_PRICE },
@@ -87,110 +87,112 @@ namespace OpenRCT2::Scripting
{ "food_drink_free", VOUCHER_TYPE_FOOD_OR_DRINK_FREE },
});
class ScThought
class ScThought;
extern ScThought gScThought;
class ScThought final : public ScBase
{
private:
PeepThought _backing;
public:
ScThought(PeepThought backing);
static void Register(duk_context* ctx);
JSValue New(JSContext* ctx, PeepThought thought);
void Register(JSContext* ctx);
private:
std::string type_get() const;
uint16_t item_get() const;
uint8_t freshness_get() const;
uint8_t freshTimeout_get() const;
std::string toString() const;
static void Finalize(JSRuntime* rt, JSValue thisVal);
static PeepThought GetThought(JSValue thisVal);
static JSValue type_get(JSContext* ctx, JSValue thisVal);
static JSValue item_get(JSContext* ctx, JSValue thisVal);
static JSValue freshness_get(JSContext* ctx, JSValue thisVal);
static JSValue freshTimeout_get(JSContext* ctx, JSValue thisVal);
static JSValue toString(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
};
class ScGuest : public ScPeep
class ScGuest final : public ScPeep
{
public:
ScGuest(EntityId id);
static void Register(duk_context* ctx);
static JSValue New(JSContext* ctx, EntityId entityId);
private:
Guest* GetGuest() const;
static void AddFuncs(JSContext* ctx, JSValue obj);
static Guest* GetGuest(JSValue thisVal);
uint8_t tshirtColour_get() const;
void tshirtColour_set(uint8_t value);
static JSValue tshirtColour_get(JSContext* ctx, JSValue thisVal);
static JSValue tshirtColour_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t trousersColour_get() const;
void trousersColour_set(uint8_t value);
static JSValue trousersColour_get(JSContext* ctx, JSValue thisVal);
static JSValue trousersColour_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t balloonColour_get() const;
void balloonColour_set(uint8_t value);
static JSValue balloonColour_get(JSContext* ctx, JSValue thisVal);
static JSValue balloonColour_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t hatColour_get() const;
void hatColour_set(uint8_t value);
static JSValue hatColour_get(JSContext* ctx, JSValue thisVal);
static JSValue hatColour_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t umbrellaColour_get() const;
void umbrellaColour_set(uint8_t value);
static JSValue umbrellaColour_get(JSContext* ctx, JSValue thisVal);
static JSValue umbrellaColour_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t happiness_get() const;
void happiness_set(uint8_t value);
static JSValue happiness_get(JSContext* ctx, JSValue thisVal);
static JSValue happiness_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t happinessTarget_get() const;
void happinessTarget_set(uint8_t value);
static JSValue happinessTarget_get(JSContext* ctx, JSValue thisVal);
static JSValue happinessTarget_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t nausea_get() const;
void nausea_set(uint8_t value);
static JSValue nausea_get(JSContext* ctx, JSValue thisVal);
static JSValue nausea_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t nauseaTarget_get() const;
void nauseaTarget_set(uint8_t value);
static JSValue nauseaTarget_get(JSContext* ctx, JSValue thisVal);
static JSValue nauseaTarget_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t hunger_get() const;
void hunger_set(uint8_t value);
static JSValue hunger_get(JSContext* ctx, JSValue thisVal);
static JSValue hunger_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t thirst_get() const;
void thirst_set(uint8_t value);
static JSValue thirst_get(JSContext* ctx, JSValue thisVal);
static JSValue thirst_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t toilet_get() const;
void toilet_set(uint8_t value);
static JSValue toilet_get(JSContext* ctx, JSValue thisVal);
static JSValue toilet_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t mass_get() const;
void mass_set(uint8_t value);
static JSValue mass_get(JSContext* ctx, JSValue thisVal);
static JSValue mass_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t minIntensity_get() const;
void minIntensity_set(uint8_t value);
static JSValue minIntensity_get(JSContext* ctx, JSValue thisVal);
static JSValue minIntensity_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t maxIntensity_get() const;
void maxIntensity_set(uint8_t value);
static JSValue maxIntensity_get(JSContext* ctx, JSValue thisVal);
static JSValue maxIntensity_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t nauseaTolerance_get() const;
void nauseaTolerance_set(uint8_t value);
static JSValue nauseaTolerance_get(JSContext* ctx, JSValue thisVal);
static JSValue nauseaTolerance_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
int32_t cash_get() const;
void cash_set(int32_t value);
static JSValue cash_get(JSContext* ctx, JSValue thisVal);
static JSValue cash_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
bool isInPark_get() const;
static JSValue isInPark_get(JSContext* ctx, JSValue thisVal);
bool isLost_get() const;
static JSValue isLost_get(JSContext* ctx, JSValue thisVal);
uint8_t lostCountdown_get() const;
void lostCountdown_set(uint8_t value);
static JSValue lostCountdown_get(JSContext* ctx, JSValue thisVal);
static JSValue lostCountdown_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
DukValue favouriteRide_get() const;
void favouriteRide_set(const DukValue& value);
static JSValue favouriteRide_get(JSContext* ctx, JSValue thisVal);
static JSValue favouriteRide_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
DukValue thoughts_get() const;
static JSValue thoughts_get(JSContext* ctx, JSValue thisVal);
DukValue items_get() const;
bool has_item(const DukValue& item) const;
void give_item(const DukValue& item) const;
void remove_item(const DukValue& item) const;
void remove_all_items() const;
static JSValue items_get(JSContext* ctx, JSValue thisVal);
static bool has_item(JSContext* ctx, JSValue thisVal, JSValue jsValue);
static JSValue has_item(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue give_item(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue remove_item(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue remove_all_items(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
std::vector<std::string> availableAnimations_get() const;
std::vector<uint32_t> getAnimationSpriteIds(std::string groupKey, uint8_t rotation) const;
std::string animation_get() const;
void animation_set(std::string groupKey);
uint8_t animationOffset_get() const;
void animationOffset_set(uint8_t offset);
uint8_t animationLength_get() const;
static JSValue availableAnimations_get(JSContext* ctx, JSValue thisVal);
static JSValue getAnimationSpriteIds(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue animation_get(JSContext* ctx, JSValue thisVal);
static JSValue animation_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
static JSValue animationOffset_get(JSContext* ctx, JSValue thisVal);
static JSValue animationOffset_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
static JSValue animationLength_get(JSContext* ctx, JSValue thisVal);
};
} // namespace OpenRCT2::Scripting
@@ -11,11 +11,12 @@
#include "ScLitter.hpp"
#include "../../../core/EnumMap.hpp"
#include "../../../entity/Litter.h"
namespace OpenRCT2::Scripting
{
static const DukEnumMap<Litter::Type> LitterTypeMap(
static const EnumMap<Litter::Type> LitterTypeMap(
{
{ "vomit", Litter::Type::vomit },
{ "vomit_alt", Litter::Type::vomitAlt },
@@ -31,57 +32,62 @@ namespace OpenRCT2::Scripting
{ "empty_bowl_blue", Litter::Type::emptyBowlBlue },
});
ScLitter::ScLitter(EntityId Id)
: ScEntity(Id)
JSValue ScLitter::New(JSContext* ctx, EntityId entityId)
{
JSValue obj = gScEntity.New(ctx, entityId);
AddFuncs(ctx, obj);
return obj;
}
void ScLitter::Register(duk_context* ctx)
void ScLitter::AddFuncs(JSContext* ctx, JSValue obj)
{
dukglue_set_base_class<ScEntity, ScLitter>(ctx);
dukglue_register_property(ctx, &ScLitter::litterType_get, &ScLitter::litterType_set, "litterType");
dukglue_register_property(ctx, &ScLitter::creationTick_get, nullptr, "creationTick");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("litterType", &ScLitter::litterType_get, &ScLitter::litterType_set),
JS_CGETSET_DEF("creationTick", &ScLitter::creationTick_get, nullptr)
};
JS_SetPropertyFunctionList(ctx, obj, funcs, std::size(funcs));
}
Litter* ScLitter::GetLitter() const
Litter* ScLitter::GetLitter(JSValue thisVal)
{
return getGameState().entities.GetEntity<Litter>(_id);
auto id = GetEntityId(thisVal);
return getGameState().entities.GetEntity<Litter>(id);
}
std::string ScLitter::litterType_get() const
JSValue ScLitter::litterType_get(JSContext* ctx, JSValue thisVal)
{
auto* litter = GetLitter();
auto* litter = GetLitter(thisVal);
if (litter != nullptr)
{
auto it = LitterTypeMap.find(litter->SubType);
if (it != LitterTypeMap.end())
{
return std::string{ it->first };
return JSFromStdString(ctx, it->first);
}
}
return {};
return JS_UNDEFINED;
}
void ScLitter::litterType_set(const std::string& litterType)
JSValue ScLitter::litterType_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
JS_UNPACK_STR(litterType, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto it = LitterTypeMap.find(litterType);
if (it == LitterTypeMap.end())
return;
auto* litter = GetLitter();
litter->SubType = it->second;
litter->Invalidate();
if (it != LitterTypeMap.end())
{
auto* litter = GetLitter(thisVal);
litter->SubType = it->second;
litter->Invalidate();
}
return JS_UNDEFINED;
}
uint32_t ScLitter::creationTick_get() const
JSValue ScLitter::creationTick_get(JSContext* ctx, JSValue thisVal)
{
auto* litter = GetLitter();
if (litter == nullptr)
return 0;
return litter->creationTick;
auto* litter = GetLitter(thisVal);
return JS_NewUint32(ctx, litter == nullptr ? 0 : litter->creationTick);
}
} // namespace OpenRCT2::Scripting
#endif
@@ -20,22 +20,20 @@ namespace OpenRCT2
namespace OpenRCT2::Scripting
{
class ScLitter : public ScEntity
class ScLitter final : public ScEntity
{
public:
ScLitter(EntityId Id);
static void Register(duk_context* ctx);
static JSValue New(JSContext* ctx, EntityId entityId);
private:
Litter* GetLitter() const;
static void AddFuncs(JSContext* ctx, JSValue obj);
static Litter* GetLitter(JSValue thisVal);
std::string litterType_get() const;
void litterType_set(const std::string& litterType);
static JSValue litterType_get(JSContext* ctx, JSValue thisVal);
static JSValue litterType_set(JSContext* ctx, JSValue thisVal, JSValue value);
uint32_t creationTick_get() const;
static JSValue creationTick_get(JSContext* ctx, JSValue thisVal);
};
} // namespace OpenRCT2::Scripting
#endif
@@ -16,41 +16,43 @@
namespace OpenRCT2::Scripting
{
ScMoneyEffect::ScMoneyEffect(EntityId Id)
: ScEntity(Id)
JSValue ScMoneyEffect::New(JSContext* ctx, EntityId entityId)
{
JSValue obj = gScEntity.New(ctx, entityId);
AddFuncs(ctx, obj);
return obj;
}
void ScMoneyEffect::Register(duk_context* ctx)
void ScMoneyEffect::AddFuncs(JSContext* ctx, JSValue obj)
{
dukglue_set_base_class<ScEntity, ScMoneyEffect>(ctx);
dukglue_register_property(ctx, &ScMoneyEffect::value_get, &ScMoneyEffect::value_set, "value");
static constexpr JSCFunctionListEntry funcs[] = { JS_CGETSET_DEF(
"value", &ScMoneyEffect::value_get, &ScMoneyEffect::value_set) };
JS_SetPropertyFunctionList(ctx, obj, funcs, std::size(funcs));
}
MoneyEffect* ScMoneyEffect::GetMoneyEffect() const
MoneyEffect* ScMoneyEffect::GetMoneyEffect(JSValue thisVal)
{
return getGameState().entities.GetEntity<MoneyEffect>(_id);
auto id = GetEntityId(thisVal);
return getGameState().entities.GetEntity<MoneyEffect>(id);
}
money64 ScMoneyEffect::value_get() const
JSValue ScMoneyEffect::value_get(JSContext* ctx, JSValue thisVal)
{
auto moneyEffect = GetMoneyEffect();
if (moneyEffect != nullptr)
{
return moneyEffect->Value;
}
return 0;
auto moneyEffect = GetMoneyEffect(thisVal);
return JS_NewUint32(ctx, moneyEffect == nullptr ? 0 : moneyEffect->Value);
}
void ScMoneyEffect::value_set(money64 value)
JSValue ScMoneyEffect::value_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
auto moneyEffect = GetMoneyEffect();
JS_UNPACK_MONEY64(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto moneyEffect = GetMoneyEffect(thisVal);
if (moneyEffect != nullptr)
{
moneyEffect->SetValue(value);
}
return JS_UNDEFINED;
}
} // namespace OpenRCT2::Scripting
#endif
@@ -20,20 +20,17 @@ namespace OpenRCT2
namespace OpenRCT2::Scripting
{
class ScMoneyEffect : public ScEntity
class ScMoneyEffect final : public ScEntity
{
public:
ScMoneyEffect(EntityId Id);
static void Register(duk_context* ctx);
static JSValue New(JSContext* ctx, EntityId entityId);
private:
MoneyEffect* GetMoneyEffect() const;
static void AddFuncs(JSContext* ctx, JSValue obj);
static MoneyEffect* GetMoneyEffect(JSValue thisVal);
money64 value_get() const;
void value_set(money64);
static JSValue value_get(JSContext* ctx, JSValue thisVal);
static JSValue value_set(JSContext* ctx, JSValue thisVal, JSValue value);
};
} // namespace OpenRCT2::Scripting
#endif
@@ -9,13 +9,14 @@
#include "ScParticle.hpp"
#include "../../../core/EnumMap.hpp"
#include "../ride/ScRide.hpp"
#ifdef ENABLE_SCRIPTING
namespace OpenRCT2::Scripting
{
static const DukEnumMap<uint8_t> CrashParticleTypeMap(
static const EnumMap<uint8_t> CrashParticleTypeMap(
{
{ "corner", 0 },
{ "rod", 1 },
@@ -24,206 +25,232 @@ namespace OpenRCT2::Scripting
{ "seat", 4 },
});
ScCrashedVehicleParticle::ScCrashedVehicleParticle(EntityId id)
: ScEntity(id)
JSValue ScCrashedVehicleParticle::New(JSContext* ctx, EntityId entityId)
{
JSValue obj = gScEntity.New(ctx, entityId);
AddFuncs(ctx, obj);
return obj;
}
void ScCrashedVehicleParticle::Register(duk_context* ctx)
void ScCrashedVehicleParticle::AddFuncs(JSContext* ctx, JSValue obj)
{
dukglue_set_base_class<ScEntity, ScCrashedVehicleParticle>(ctx);
dukglue_register_property(
ctx, &ScCrashedVehicleParticle::acceleration_get, &ScCrashedVehicleParticle::acceleration_set, "acceleration");
dukglue_register_property(
ctx, &ScCrashedVehicleParticle::velocity_get, &ScCrashedVehicleParticle::velocity_set, "velocity");
dukglue_register_property(
ctx, &ScCrashedVehicleParticle::colours_get, &ScCrashedVehicleParticle::colours_set, "colours");
dukglue_register_property(
ctx, &ScCrashedVehicleParticle::timeToLive_get, &ScCrashedVehicleParticle::timeToLive_set, "timeToLive");
dukglue_register_property(
ctx, &ScCrashedVehicleParticle::crashedSpriteBase_get, &ScCrashedVehicleParticle::crashedSpriteBase_set,
"crashParticleType");
dukglue_register_property(ctx, &ScCrashedVehicleParticle::frame_get, &ScCrashedVehicleParticle::frame_set, "frame");
dukglue_register_method(ctx, &ScCrashedVehicleParticle::Launch, "launch");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF(
"acceleration", &ScCrashedVehicleParticle::acceleration_get, &ScCrashedVehicleParticle::acceleration_set),
JS_CGETSET_DEF("velocity", &ScCrashedVehicleParticle::velocity_get, &ScCrashedVehicleParticle::velocity_set),
JS_CGETSET_DEF("colours", &ScCrashedVehicleParticle::colours_get, &ScCrashedVehicleParticle::colours_set),
JS_CGETSET_DEF("timeToLive", &ScCrashedVehicleParticle::timeToLive_get, &ScCrashedVehicleParticle::timeToLive_set),
JS_CGETSET_DEF(
"crashParticleType", &ScCrashedVehicleParticle::crashedSpriteBase_get,
&ScCrashedVehicleParticle::crashedSpriteBase_set),
JS_CGETSET_DEF("frame", &ScCrashedVehicleParticle::frame_get, &ScCrashedVehicleParticle::frame_set),
JS_CFUNC_DEF("launch", 1, &ScCrashedVehicleParticle::Launch),
};
JS_SetPropertyFunctionList(ctx, obj, funcs, std::size(funcs));
}
VehicleCrashParticle* ScCrashedVehicleParticle::GetCrashedVehicleParticle() const
VehicleCrashParticle* ScCrashedVehicleParticle::GetCrashedVehicleParticle(JSValue thisVal)
{
return getGameState().entities.GetEntity<VehicleCrashParticle>(_id);
auto id = GetEntityId(thisVal);
return getGameState().entities.GetEntity<VehicleCrashParticle>(id);
}
void ScCrashedVehicleParticle::frame_set(uint8_t value)
JSValue ScCrashedVehicleParticle::frame_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
auto entity = GetCrashedVehicleParticle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto entity = GetCrashedVehicleParticle(thisVal);
if (entity != nullptr)
{
entity->frame = std::clamp<uint16_t>(value, 0, kCrashedVehicleParticleNumberSprites - 1)
* kCrashedVehicleParticleFrameToSprite;
entity->Invalidate();
}
return JS_UNDEFINED;
}
uint8_t ScCrashedVehicleParticle::frame_get() const
JSValue ScCrashedVehicleParticle::frame_get(JSContext* ctx, JSValue thisVal)
{
auto entity = GetCrashedVehicleParticle();
if (entity != nullptr)
{
return entity->frame / kCrashedVehicleParticleFrameToSprite;
}
return 0;
auto entity = GetCrashedVehicleParticle(thisVal);
auto frame = (entity != nullptr) ? entity->frame / kCrashedVehicleParticleFrameToSprite : 0;
return JS_NewUint32(ctx, frame);
}
void ScCrashedVehicleParticle::crashedSpriteBase_set(const std::string& value)
JSValue ScCrashedVehicleParticle::crashedSpriteBase_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
auto entity = GetCrashedVehicleParticle();
JS_UNPACK_STR(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto entity = GetCrashedVehicleParticle(thisVal);
if (entity != nullptr)
{
entity->crashed_sprite_base = CrashParticleTypeMap[value];
entity->Invalidate();
}
return JS_UNDEFINED;
}
std::string ScCrashedVehicleParticle::crashedSpriteBase_get() const
JSValue ScCrashedVehicleParticle::crashedSpriteBase_get(JSContext* ctx, JSValue thisVal)
{
auto entity = GetCrashedVehicleParticle();
auto entity = GetCrashedVehicleParticle(thisVal);
if (entity != nullptr)
{
return std::string(CrashParticleTypeMap[entity->crashed_sprite_base]);
return JSFromStdString(ctx, CrashParticleTypeMap[entity->crashed_sprite_base]);
}
return {};
return JS_UNDEFINED;
}
void ScCrashedVehicleParticle::timeToLive_set(uint16_t value)
JSValue ScCrashedVehicleParticle::timeToLive_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
auto entity = GetCrashedVehicleParticle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto entity = GetCrashedVehicleParticle(thisVal);
if (entity != nullptr)
{
entity->time_to_live = value;
}
return JS_UNDEFINED;
}
uint16_t ScCrashedVehicleParticle::timeToLive_get() const
JSValue ScCrashedVehicleParticle::timeToLive_get(JSContext* ctx, JSValue thisVal)
{
auto entity = GetCrashedVehicleParticle();
if (entity != nullptr)
{
return entity->time_to_live;
}
return 0;
auto entity = GetCrashedVehicleParticle(thisVal);
return JS_NewUint32(ctx, entity == nullptr ? 0 : entity->time_to_live);
}
void ScCrashedVehicleParticle::velocity_set(const DukValue& value)
JSValue ScCrashedVehicleParticle::velocity_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
auto entity = GetCrashedVehicleParticle();
JS_UNPACK_OBJECT(obj, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto entity = GetCrashedVehicleParticle(thisVal);
if (entity != nullptr)
{
auto velocity = FromDuk<CoordsXYZ>(value);
auto velocity = JSToCoordsXYZ(ctx, obj);
entity->velocity_x = velocity.x;
entity->velocity_y = velocity.y;
entity->velocity_z = velocity.z;
}
return JS_UNDEFINED;
}
DukValue ScCrashedVehicleParticle::velocity_get() const
JSValue ScCrashedVehicleParticle::velocity_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto entity = GetCrashedVehicleParticle();
auto entity = GetCrashedVehicleParticle(thisVal);
if (entity != nullptr)
{
return ToDuk(ctx, CoordsXYZ(entity->velocity_x, entity->velocity_y, entity->velocity_z));
return ToJSValue(ctx, CoordsXYZ(entity->velocity_x, entity->velocity_y, entity->velocity_z));
}
return {};
return JS_UNDEFINED;
}
void ScCrashedVehicleParticle::acceleration_set(const DukValue& value)
JSValue ScCrashedVehicleParticle::acceleration_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
auto entity = GetCrashedVehicleParticle();
JS_UNPACK_OBJECT(obj, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto entity = GetCrashedVehicleParticle(thisVal);
if (entity != nullptr)
{
auto acceleration = FromDuk<CoordsXYZ>(value);
auto acceleration = JSToCoordsXYZ(ctx, obj);
entity->acceleration_x = acceleration.x;
entity->acceleration_y = acceleration.y;
entity->acceleration_z = acceleration.z;
}
return JS_UNDEFINED;
}
DukValue ScCrashedVehicleParticle::acceleration_get() const
JSValue ScCrashedVehicleParticle::acceleration_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto entity = GetCrashedVehicleParticle();
auto entity = GetCrashedVehicleParticle(thisVal);
if (entity != nullptr)
{
return ToDuk(ctx, CoordsXYZ(entity->acceleration_x, entity->acceleration_y, entity->acceleration_z));
return ToJSValue(ctx, CoordsXYZ(entity->acceleration_x, entity->acceleration_y, entity->acceleration_z));
}
return {};
return JS_UNDEFINED;
}
void ScCrashedVehicleParticle::Launch(const DukValue& value)
JSValue ScCrashedVehicleParticle::Launch(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto entity = GetCrashedVehicleParticle();
JS_UNPACK_OBJECT(obj, ctx, argv[0]);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto entity = GetCrashedVehicleParticle(thisVal);
if (entity != nullptr)
{
entity->SetSpriteData();
entity->Launch();
if (value.type() == DukValue::Type::UNDEFINED)
return;
if (JS_IsUndefined(obj))
return JS_UNDEFINED;
if (value["colours"].type() == DukValue::Type::OBJECT)
auto colours = JS_GetPropertyStr(ctx, obj, "colours");
auto acceleration = JS_GetPropertyStr(ctx, obj, "acceleration");
auto velocity = JS_GetPropertyStr(ctx, obj, "velocity");
auto timeToLive = JS_GetPropertyStr(ctx, obj, "timeToLive");
auto frame = JS_GetPropertyStr(ctx, obj, "frame");
auto crashParticleType = JS_GetPropertyStr(ctx, obj, "crashParticleType");
if (JS_IsObject(colours))
{
auto coloursInt = FromDuk<VehicleColour>(value["colours"]);
entity->colour[0] = coloursInt.Body;
entity->colour[1] = coloursInt.Trim;
entity->colour[0] = static_cast<Drawing::Colour>(JSToUint(ctx, colours, "body"));
entity->colour[1] = static_cast<Drawing::Colour>(JSToUint(ctx, colours, "trim"));
}
if (value["acceleration"].type() == DukValue::Type::OBJECT)
if (JS_IsObject(acceleration))
{
auto accelerationXYZ = FromDuk<CoordsXYZ>(value["acceleration"]);
auto accelerationXYZ = JSToCoordsXYZ(ctx, acceleration);
entity->acceleration_x = accelerationXYZ.x;
entity->acceleration_y = accelerationXYZ.y;
entity->acceleration_z = accelerationXYZ.z;
}
if (value["velocity"].type() == DukValue::Type::OBJECT)
if (JS_IsObject(velocity))
{
auto velocityXYZ = FromDuk<CoordsXYZ>(value["velocity"]);
auto velocityXYZ = JSToCoordsXYZ(ctx, velocity);
entity->velocity_x = velocityXYZ.x;
entity->velocity_y = velocityXYZ.y;
entity->velocity_z = velocityXYZ.z;
}
if (value["timeToLive"].type() == DukValue::Type::NUMBER)
if (JS_IsNumber(timeToLive))
{
entity->time_to_live = value["timeToLive"].as_uint();
entity->time_to_live = JSToUint(ctx, timeToLive);
}
if (value["frame"].type() == DukValue::Type::NUMBER)
if (JS_IsNumber(frame))
{
entity->frame = std::clamp<uint16_t>(value["frame"].as_uint(), 0, kCrashedVehicleParticleNumberSprites - 1)
entity->frame = std::clamp<uint16_t>(JSToUint(ctx, frame), 0, kCrashedVehicleParticleNumberSprites - 1)
* kCrashedVehicleParticleFrameToSprite;
}
if (value["crashParticleType"].type() == DukValue::Type::STRING)
if (JS_IsString(crashParticleType))
{
entity->crashed_sprite_base = CrashParticleTypeMap[value["crashParticleType"].as_string()];
auto key = JSToStdString(ctx, crashParticleType);
entity->crashed_sprite_base = CrashParticleTypeMap[key];
}
entity->Invalidate();
JS_FreeValue(ctx, colours);
JS_FreeValue(ctx, acceleration);
JS_FreeValue(ctx, velocity);
JS_FreeValue(ctx, timeToLive);
JS_FreeValue(ctx, frame);
JS_FreeValue(ctx, crashParticleType);
}
return JS_UNDEFINED;
}
DukValue ScCrashedVehicleParticle::colours_get() const
JSValue ScCrashedVehicleParticle::colours_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto entity = GetCrashedVehicleParticle();
auto entity = GetCrashedVehicleParticle(thisVal);
if (entity != nullptr)
{
DukObject dukColour(ctx);
dukColour.Set("body", EnumValue(entity->colour[0]));
dukColour.Set("trim", EnumValue(entity->colour[1]));
return dukColour.Take();
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "body", JS_NewInt32(ctx, EnumValue(entity->colour[0])));
JS_SetPropertyStr(ctx, obj, "trim", JS_NewInt32(ctx, EnumValue(entity->colour[1])));
return obj;
}
return ToDuk(ctx, nullptr);
return JS_NULL;
}
void ScCrashedVehicleParticle::colours_set(const DukValue& value)
JSValue ScCrashedVehicleParticle::colours_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
auto entity = GetCrashedVehicleParticle();
JS_UNPACK_OBJECT(obj, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto entity = GetCrashedVehicleParticle(thisVal);
if (entity != nullptr)
{
auto colours = FromDuk<VehicleColour>(value);
entity->colour[0] = colours.Body;
entity->colour[1] = colours.Trim;
entity->colour[0] = static_cast<Drawing::Colour>(JSToUint(ctx, obj, "body"));
entity->colour[1] = static_cast<Drawing::Colour>(JSToUint(ctx, obj, "trim"));
entity->Invalidate();
}
return JS_UNDEFINED;
}
} // namespace OpenRCT2::Scripting
@@ -19,35 +19,34 @@
namespace OpenRCT2::Scripting
{
class ScCrashedVehicleParticle : public ScEntity
class ScCrashedVehicleParticle final : public ScEntity
{
public:
ScCrashedVehicleParticle(EntityId id);
static void Register(duk_context* ctx);
static JSValue New(JSContext* ctx, EntityId entityId);
private:
VehicleCrashParticle* GetCrashedVehicleParticle() const;
static void AddFuncs(JSContext* ctx, JSValue obj);
static VehicleCrashParticle* GetCrashedVehicleParticle(JSValue thisVal);
DukValue colours_get() const;
void colours_set(const DukValue& value);
static JSValue colours_get(JSContext* ctx, JSValue thisVal);
static JSValue colours_set(JSContext* ctx, JSValue thisVal, JSValue value);
DukValue acceleration_get() const;
void acceleration_set(const DukValue& value);
static JSValue acceleration_get(JSContext* ctx, JSValue thisVal);
static JSValue acceleration_set(JSContext* ctx, JSValue thisVal, JSValue value);
DukValue velocity_get() const;
void velocity_set(const DukValue& value);
static JSValue velocity_get(JSContext* ctx, JSValue thisVal);
static JSValue velocity_set(JSContext* ctx, JSValue thisVal, JSValue value);
uint8_t frame_get() const;
void frame_set(uint8_t value);
static JSValue frame_get(JSContext* ctx, JSValue thisVal);
static JSValue frame_set(JSContext* ctx, JSValue thisVal, JSValue value);
void crashedSpriteBase_set(const std::string& value);
std::string crashedSpriteBase_get() const;
static JSValue crashedSpriteBase_get(JSContext* ctx, JSValue thisVal);
static JSValue crashedSpriteBase_set(JSContext* ctx, JSValue thisVal, JSValue value);
void timeToLive_set(uint16_t value);
uint16_t timeToLive_get() const;
static JSValue timeToLive_get(JSContext* ctx, JSValue thisVal);
static JSValue timeToLive_set(JSContext* ctx, JSValue thisVal, JSValue value);
void Launch(const DukValue& value);
static JSValue Launch(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
};
} // namespace OpenRCT2::Scripting
@@ -11,11 +11,12 @@
#ifdef ENABLE_SCRIPTING
#include "../../../core/EnumMap.hpp"
#include "ScEntity.hpp"
namespace OpenRCT2::Scripting
{
static const DukEnumMap<uint32_t> PeepFlagMap(
static const EnumMap<uint32_t> PeepFlagMap(
{
{ "leavingPark", PEEP_FLAGS_LEAVING_PARK },
{ "slowWalk", PEEP_FLAGS_SLOW_WALK },
@@ -49,65 +50,74 @@ namespace OpenRCT2::Scripting
class ScPeep : public ScEntity
{
public:
ScPeep(EntityId id)
: ScEntity(id)
static JSValue New(JSContext* ctx, EntityId entityId)
{
}
static void Register(duk_context* ctx)
{
dukglue_set_base_class<ScEntity, ScPeep>(ctx);
dukglue_register_property(ctx, &ScPeep::peepType_get, nullptr, "peepType");
dukglue_register_property(ctx, &ScPeep::name_get, &ScPeep::name_set, "name");
dukglue_register_property(ctx, &ScPeep::destination_get, &ScPeep::destination_set, "destination");
dukglue_register_property(ctx, &ScPeep::direction_get, &ScPeep::direction_set, "direction");
dukglue_register_property(ctx, &ScPeep::energy_get, &ScPeep::energy_set, "energy");
dukglue_register_property(ctx, &ScPeep::energyTarget_get, &ScPeep::energyTarget_set, "energyTarget");
dukglue_register_method(ctx, &ScPeep::getFlag, "getFlag");
dukglue_register_method(ctx, &ScPeep::setFlag, "setFlag");
JSValue obj = gScEntity.New(ctx, entityId);
AddFuncs(ctx, obj);
return obj;
}
private:
std::string peepType_get() const
static void AddFuncs(JSContext* ctx, JSValue obj)
{
auto peep = GetPeep();
if (peep != nullptr)
{
return peep->Is<Staff>() ? "staff" : "guest";
}
return {};
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("peepType", &ScPeep::peepType_get, nullptr),
JS_CGETSET_DEF("name", &ScPeep::name_get, &ScPeep::name_set),
JS_CGETSET_DEF("destination", &ScPeep::destination_get, &ScPeep::destination_set),
JS_CGETSET_DEF("direction", &ScPeep::direction_get, &ScPeep::direction_set),
JS_CGETSET_DEF("energy", &ScPeep::energy_get, &ScPeep::energy_set),
JS_CGETSET_DEF("energyTarget", &ScPeep::energyTarget_get, &ScPeep::energyTarget_set),
JS_CFUNC_DEF("getFlag", 1, &ScPeep::getFlag),
JS_CFUNC_DEF("setFlag", 2, &ScPeep::setFlag),
};
JS_SetPropertyFunctionList(ctx, obj, funcs, std::size(funcs));
}
std::string name_get() const
static JSValue peepType_get(JSContext* ctx, JSValue thisVal)
{
auto peep = GetPeep();
return peep != nullptr ? peep->GetName() : std::string();
auto peep = GetPeep(thisVal);
if (peep != nullptr)
{
return JSFromStdString(ctx, peep->Is<Staff>() ? "staff" : "guest");
}
return JS_UNDEFINED;
}
void name_set(const std::string& value)
static JSValue name_get(JSContext* ctx, JSValue thisVal)
{
ThrowIfGameStateNotMutable();
auto peep = GetPeep();
auto peep = GetPeep(thisVal);
return JSFromStdString(ctx, peep != nullptr ? peep->GetName() : std::string());
}
static JSValue name_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
JS_UNPACK_STR(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto peep = GetPeep(thisVal);
if (peep != nullptr)
{
peep->SetName(value);
}
return JS_UNDEFINED;
}
bool getFlag(const std::string& key) const
static JSValue getFlag(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto peep = GetPeep();
JS_UNPACK_STR(key, ctx, argv[0]);
auto peep = GetPeep(thisVal);
if (peep != nullptr)
{
auto mask = PeepFlagMap[key];
return (peep->PeepFlags & mask) != 0;
return JS_NewBool(ctx, (peep->PeepFlags & mask) != 0);
}
return false;
return JS_NewBool(ctx, false);
}
void setFlag(const std::string& key, bool value)
static JSValue setFlag(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
ThrowIfGameStateNotMutable();
auto peep = GetPeep();
JS_UNPACK_STR(key, ctx, argv[0]);
JS_UNPACK_BOOL(value, ctx, argv[1]);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto peep = GetPeep(thisVal);
if (peep != nullptr)
{
auto mask = PeepFlagMap[key];
@@ -117,86 +127,95 @@ namespace OpenRCT2::Scripting
peep->PeepFlags &= ~mask;
peep->Invalidate();
}
return JS_UNDEFINED;
}
DukValue destination_get() const
static JSValue destination_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto peep = GetPeep();
auto peep = GetPeep(thisVal);
if (peep != nullptr)
{
return ToDuk(ctx, peep->GetDestination());
return ToJSValue(ctx, peep->GetDestination());
}
return ToDuk(ctx, nullptr);
return JS_NULL;
}
void destination_set(const DukValue& value)
static JSValue destination_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto peep = GetPeep();
JS_UNPACK_OBJECT(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto peep = GetPeep(thisVal);
if (peep != nullptr)
{
auto pos = FromDuk<CoordsXY>(value);
auto pos = JSToCoordsXY(ctx, value);
peep->SetDestination(pos);
peep->Invalidate();
}
return JS_UNDEFINED;
}
uint8_t direction_get() const
static JSValue direction_get(JSContext* ctx, JSValue thisVal)
{
auto peep = GetPeep();
return peep != nullptr ? peep->PeepDirection : 0;
auto peep = GetPeep(thisVal);
return JS_NewUint32(ctx, peep != nullptr ? peep->PeepDirection : 0);
}
void direction_set(const uint8_t value)
static JSValue direction_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto peep = GetPeep();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto peep = GetPeep(thisVal);
if (peep != nullptr && value < kNumOrthogonalDirections)
{
peep->PeepDirection = value;
peep->Orientation = value << 3;
peep->Invalidate();
}
return JS_UNDEFINED;
}
uint8_t energy_get() const
static JSValue energy_get(JSContext* ctx, JSValue thisVal)
{
auto peep = GetPeep();
return peep != nullptr ? peep->Energy : 0;
auto peep = GetPeep(thisVal);
return JS_NewUint32(ctx, peep != nullptr ? peep->Energy : 0);
}
void energy_set(uint8_t value)
static JSValue energy_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto peep = GetPeep();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto peep = GetPeep(thisVal);
if (peep != nullptr)
{
value = std::clamp(value, kPeepMinEnergy, kPeepMaxEnergy);
value = std::clamp(static_cast<uint8_t>(value), kPeepMinEnergy, kPeepMaxEnergy);
peep->Energy = value;
peep->Invalidate();
}
return JS_UNDEFINED;
}
uint8_t energyTarget_get() const
static JSValue energyTarget_get(JSContext* ctx, JSValue thisVal)
{
auto peep = GetPeep();
return peep != nullptr ? peep->EnergyTarget : 0;
auto peep = GetPeep(thisVal);
return JS_NewUint32(ctx, peep != nullptr ? peep->EnergyTarget : 0);
}
void energyTarget_set(uint8_t value)
static JSValue energyTarget_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto peep = GetPeep();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto peep = GetPeep(thisVal);
if (peep != nullptr)
{
value = std::clamp(value, kPeepMinEnergy, kPeepMaxEnergyTarget);
peep->EnergyTarget = value;
auto target = std::clamp(static_cast<uint8_t>(value), kPeepMinEnergy, kPeepMaxEnergyTarget);
peep->EnergyTarget = target;
}
return JS_UNDEFINED;
}
protected:
Peep* GetPeep() const
static Peep* GetPeep(JSValue thisVal)
{
return getGameState().entities.GetEntity<Peep>(_id);
auto id = GetEntityId(thisVal);
return getGameState().entities.GetEntity<Peep>(id);
}
};
+256 -233
View File
@@ -21,59 +21,65 @@
namespace OpenRCT2::Scripting
{
ScStaff::ScStaff(EntityId Id)
: ScPeep(Id)
JSValue ScStaff::New(JSContext* ctx, EntityId entityId)
{
JSValue obj = ScPeep::New(ctx, entityId);
AddFuncs(ctx, obj);
return obj;
}
void ScStaff::Register(duk_context* ctx)
void ScStaff::AddFuncs(JSContext* ctx, JSValue obj)
{
dukglue_set_base_class<ScPeep, ScStaff>(ctx);
dukglue_register_property(ctx, &ScStaff::staffType_get, &ScStaff::staffType_set, "staffType");
dukglue_register_property(ctx, &ScStaff::colour_get, &ScStaff::colour_set, "colour");
dukglue_register_property(ctx, &ScStaff::availableCostumes_get, nullptr, "availableCostumes");
dukglue_register_property(ctx, &ScStaff::costume_get, &ScStaff::costume_set, "costume");
dukglue_register_property(ctx, &ScStaff::patrolArea_get, nullptr, "patrolArea");
dukglue_register_property(ctx, &ScStaff::orders_get, &ScStaff::orders_set, "orders");
dukglue_register_property(ctx, &ScStaff::availableAnimations_get, nullptr, "availableAnimations");
dukglue_register_property(ctx, &ScStaff::animation_get, &ScStaff::animation_set, "animation");
dukglue_register_property(ctx, &ScStaff::animationOffset_get, &ScStaff::animationOffset_set, "animationOffset");
dukglue_register_property(ctx, &ScStaff::animationLength_get, nullptr, "animationLength");
dukglue_register_method(ctx, &ScStaff::getAnimationSpriteIds, "getAnimationSpriteIds");
dukglue_register_method(ctx, &ScStaff::getCostumeStrings, "getCostumeStrings");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("staffType", &ScStaff::staffType_get, &ScStaff::staffType_set),
JS_CGETSET_DEF("colour", &ScStaff::colour_get, &ScStaff::colour_set),
JS_CGETSET_DEF("availableCostumes", &ScStaff::availableCostumes_get, nullptr),
JS_CGETSET_DEF("costume", &ScStaff::costume_get, &ScStaff::costume_set),
JS_CGETSET_DEF("patrolArea", &ScStaff::patrolArea_get, nullptr),
JS_CGETSET_DEF("orders", &ScStaff::orders_get, &ScStaff::orders_set),
JS_CGETSET_DEF("availableAnimations", &ScStaff::availableAnimations_get, nullptr),
JS_CGETSET_DEF("animation", &ScStaff::animation_get, &ScStaff::animation_set),
JS_CGETSET_DEF("animationOffset", &ScStaff::animationOffset_get, &ScStaff::animationOffset_set),
JS_CGETSET_DEF("animationLength", &ScStaff::animationLength_get, nullptr),
JS_CFUNC_DEF("getAnimationSpriteIds", 2, &ScStaff::getAnimationSpriteIds),
JS_CFUNC_DEF("getCostumeStrings", 0, &ScStaff::getCostumeStrings)
};
JS_SetPropertyFunctionList(ctx, obj, funcs, std::size(funcs));
}
Staff* ScStaff::GetStaff() const
Staff* ScStaff::GetStaff(JSValue thisVal)
{
return getGameState().entities.GetEntity<Staff>(_id);
auto id = GetEntityId(thisVal);
return getGameState().entities.GetEntity<Staff>(id);
}
std::string ScStaff::staffType_get() const
JSValue ScStaff::staffType_get(JSContext* ctx, JSValue thisVal)
{
auto peep = GetStaff();
auto peep = GetStaff(thisVal);
if (peep != nullptr)
{
switch (peep->AssignedStaffType)
{
case StaffType::handyman:
return "handyman";
return JSFromStdString(ctx, "handyman");
case StaffType::mechanic:
return "mechanic";
return JSFromStdString(ctx, "mechanic");
case StaffType::security:
return "security";
return JSFromStdString(ctx, "security");
case StaffType::entertainer:
return "entertainer";
return JSFromStdString(ctx, "entertainer");
case StaffType::count:
break;
}
}
return {};
return JS_UNDEFINED;
}
void ScStaff::staffType_set(const std::string& value)
JSValue ScStaff::staffType_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto peep = GetStaff();
JS_UNPACK_STR(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto peep = GetStaff(thisVal);
if (peep != nullptr)
{
if (value == "handyman" && peep->AssignedStaffType != StaffType::handyman)
@@ -106,23 +112,26 @@ namespace OpenRCT2::Scripting
peep->AnimationType = peep->NextAnimationType = PeepAnimationType::walking;
peep->Invalidate();
}
return JS_UNDEFINED;
}
uint8_t ScStaff::colour_get() const
JSValue ScStaff::colour_get(JSContext* ctx, JSValue thisVal)
{
auto peep = GetStaff();
return peep != nullptr ? EnumValue(peep->TshirtColour) : 0;
auto peep = GetStaff(thisVal);
return JS_NewUint32(ctx, peep != nullptr ? EnumValue(peep->TshirtColour) : 0);
}
void ScStaff::colour_set(uint8_t value)
JSValue ScStaff::colour_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto peep = GetStaff();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto peep = GetStaff(thisVal);
if (peep != nullptr)
{
peep->TshirtColour = static_cast<Drawing::Colour>(value);
peep->Invalidate();
}
return JS_UNDEFINED;
}
static const std::vector<AnimationGroupResult> costumesByStaffType(StaffType staffType)
@@ -132,39 +141,42 @@ namespace OpenRCT2::Scripting
return getAnimationGroupsByPeepType(animPeepType);
}
std::vector<std::string> ScStaff::availableCostumes_get() const
JSValue ScStaff::availableCostumes_get(JSContext* ctx, JSValue thisVal)
{
std::vector<std::string> availableCostumes{};
auto peep = GetStaff();
JSValue availableCostumes = JS_NewArray(ctx);
auto peep = GetStaff(thisVal);
if (peep != nullptr)
{
auto idx = 0;
for (auto& costume : costumesByStaffType(peep->AssignedStaffType))
{
availableCostumes.push_back(std::string(costume.scriptName));
JS_SetPropertyInt64(ctx, availableCostumes, idx++, JSFromStdString(ctx, costume.scriptName));
}
}
return availableCostumes;
}
std::vector<std::string> ScStaff::getCostumeStrings() const
JSValue ScStaff::getCostumeStrings(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto peep = GetStaff();
auto peep = GetStaff(thisVal);
auto animPeepType = getAnimationPeepType(peep->AssignedStaffType);
std::vector<std::string> availableCostumes{};
JSValue availableCostumes = JS_NewArray(ctx);
auto idx = 0;
for (auto& costume : getAvailableCostumeStrings(animPeepType))
{
availableCostumes.push_back(costume.friendlyName);
JS_SetPropertyInt64(ctx, availableCostumes, idx++, JSFromStdString(ctx, costume.friendlyName));
}
return availableCostumes;
}
std::string ScStaff::costume_get() const
JSValue ScStaff::costume_get(JSContext* ctx, JSValue thisVal)
{
auto peep = GetStaff();
auto peep = GetStaff(thisVal);
if (peep == nullptr)
{
return {};
return JS_UNDEFINED;
}
auto& costumes = costumesByStaffType(peep->AssignedStaffType);
@@ -175,69 +187,78 @@ namespace OpenRCT2::Scripting
if (costume != costumes.end())
{
return std::string(costume->scriptName);
return JSFromStdString(ctx, costume->scriptName);
}
else
return "";
return JS_UNDEFINED;
}
void ScStaff::costume_set(const DukValue& value)
JSValue ScStaff::costume_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto peep = GetStaff();
auto peep = GetStaff(thisVal);
if (peep == nullptr)
{
return;
return JS_UNDEFINED;
}
auto& costumes = costumesByStaffType(peep->AssignedStaffType);
auto costume = costumes.end();
// Split by type passed so as to not break old plugins
if (value.type() == DukValue::Type::STRING)
if (JS_IsString(value))
{
costume = std::find_if(costumes.begin(), costumes.end(), [value](auto& candidate) {
return candidate.scriptName == value.as_string();
JS_UNPACK_STR(valueString, ctx, value);
costume = std::find_if(costumes.begin(), costumes.end(), [valueString](auto& candidate) {
return candidate.scriptName == valueString;
});
}
else if (value.type() == DukValue::Type::NUMBER)
else if (JS_IsNumber(value))
{
auto target = RCT12PeepAnimationGroup(value.as_uint() + EnumValue(RCT12PeepAnimationGroup::entertainerPanda));
JS_UNPACK_UINT32(number, ctx, value);
auto target = RCT12PeepAnimationGroup(number + EnumValue(RCT12PeepAnimationGroup::entertainerPanda));
costume = std::find_if(
costumes.begin(), costumes.end(), [target](auto& candidate) { return candidate.legacyPosition == target; });
}
if (costume == costumes.end())
throw DukException() << "Invalid costume for this staff member";
{
JS_ThrowPlainError(ctx, "Invalid costume for this staff member");
return JS_EXCEPTION;
}
peep->AnimationObjectIndex = costume->objectId;
peep->AnimationGroup = costume->group;
peep->Invalidate();
return JS_UNDEFINED;
}
std::shared_ptr<ScPatrolArea> ScStaff::patrolArea_get() const
JSValue ScStaff::patrolArea_get(JSContext* ctx, JSValue thisVal)
{
return std::make_shared<ScPatrolArea>(_id);
auto staffId = GetEntityId(thisVal);
return gScPatrolArea.New(ctx, staffId);
}
uint8_t ScStaff::orders_get() const
JSValue ScStaff::orders_get(JSContext* ctx, JSValue thisVal)
{
auto peep = GetStaff();
return peep != nullptr ? peep->StaffOrders : 0;
auto peep = GetStaff(thisVal);
return JS_NewUint32(ctx, peep != nullptr ? peep->StaffOrders : 0);
}
void ScStaff::orders_set(uint8_t value)
JSValue ScStaff::orders_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto peep = GetStaff();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto peep = GetStaff(thisVal);
if (peep != nullptr)
{
peep->StaffOrders = value;
}
return JS_UNDEFINED;
}
const DukEnumMap<PeepAnimationType>& ScStaff::animationsByStaffType(StaffType staffType) const
EnumMap<PeepAnimationType> ScStaff::animationsByStaffType(StaffType staffType)
{
AnimationPeepType animPeepType{};
switch (staffType)
@@ -258,33 +279,36 @@ namespace OpenRCT2::Scripting
return getAnimationsByPeepType(animPeepType);
}
std::vector<std::string> ScStaff::availableAnimations_get() const
JSValue ScStaff::availableAnimations_get(JSContext* ctx, JSValue thisVal)
{
std::vector<std::string> availableAnimations{};
JSValue availableAnimations = JS_NewArray(ctx);
auto* peep = GetStaff();
auto* peep = GetStaff(thisVal);
if (peep != nullptr)
{
auto idx = 0;
for (auto& animation : animationsByStaffType(peep->AssignedStaffType))
{
availableAnimations.push_back(std::string(animation.first));
JS_SetPropertyInt64(ctx, availableAnimations, idx++, JSFromStdString(ctx, animation.first));
}
}
return availableAnimations;
}
std::vector<uint32_t> ScStaff::getAnimationSpriteIds(std::string groupKey, uint8_t rotation) const
JSValue ScStaff::getAnimationSpriteIds(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
std::vector<uint32_t> spriteIds{};
JS_UNPACK_STR(groupKey, ctx, argv[0]);
JS_UNPACK_UINT32(rotation, ctx, argv[1]);
JSValue spriteIds = JS_NewArray(ctx);
auto* peep = GetStaff();
auto* peep = GetStaff(thisVal);
if (peep == nullptr)
{
return spriteIds;
}
auto& animationGroups = animationsByStaffType(peep->AssignedStaffType);
auto animationGroups = animationsByStaffType(peep->AssignedStaffType);
auto animationType = animationGroups.TryGet(groupKey);
if (animationType == std::nullopt)
{
@@ -295,6 +319,7 @@ namespace OpenRCT2::Scripting
auto* animObj = objManager.GetLoadedObject<PeepAnimationsObject>(peep->AnimationObjectIndex);
const auto& animationGroup = animObj->GetPeepAnimation(peep->AnimationGroup, *animationType);
auto idx = 0;
for (auto frameOffset : animationGroup.frameOffsets)
{
auto imageId = animationGroup.baseImage;
@@ -303,35 +328,36 @@ namespace OpenRCT2::Scripting
else
imageId += frameOffset;
spriteIds.push_back(imageId);
JS_SetPropertyInt64(ctx, spriteIds, idx++, JS_NewUint32(ctx, imageId));
}
return spriteIds;
}
std::string ScStaff::animation_get() const
JSValue ScStaff::animation_get(JSContext* ctx, JSValue thisVal)
{
auto* peep = GetStaff();
auto* peep = GetStaff(thisVal);
if (peep == nullptr)
{
return "";
return JSFromStdString(ctx, "");
}
auto& animationGroups = animationsByStaffType(peep->AssignedStaffType);
std::string_view action = animationGroups[peep->AnimationType];
return std::string(action);
auto animationGroups = animationsByStaffType(peep->AssignedStaffType);
return JSFromStdString(ctx, animationGroups[peep->AnimationType]);
}
void ScStaff::animation_set(std::string groupKey)
JSValue ScStaff::animation_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
JS_UNPACK_STR(groupKey, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto* peep = GetStaff();
auto& animationGroups = animationsByStaffType(peep->AssignedStaffType);
auto* peep = GetStaff(thisVal);
auto animationGroups = animationsByStaffType(peep->AssignedStaffType);
auto newType = animationGroups.TryGet(groupKey);
if (newType == std::nullopt)
{
throw DukException() << "Invalid animation for this staff member (" << groupKey << ")";
JS_ThrowPlainError(ctx, "Invalid animation for this staff member (%s)", groupKey.data());
return JS_EXCEPTION;
}
peep->AnimationType = peep->NextAnimationType = *newType;
@@ -350,27 +376,27 @@ namespace OpenRCT2::Scripting
peep->Invalidate();
peep->UpdateSpriteBoundingBox();
peep->Invalidate();
return JS_UNDEFINED;
}
uint8_t ScStaff::animationOffset_get() const
JSValue ScStaff::animationOffset_get(JSContext* ctx, JSValue thisVal)
{
auto* peep = GetStaff();
auto* peep = GetStaff(thisVal);
if (peep == nullptr)
{
return 0;
return JS_NewUint32(ctx, 0);
}
if (peep->IsActionWalking())
return peep->WalkingAnimationFrameNum;
else
return peep->AnimationFrameNum;
auto frame = peep->IsActionWalking() ? peep->WalkingAnimationFrameNum : peep->AnimationFrameNum;
return JS_NewUint32(ctx, frame);
}
void ScStaff::animationOffset_set(uint8_t offset)
JSValue ScStaff::animationOffset_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
JS_UNPACK_UINT32(offset, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto* peep = GetStaff();
auto* peep = GetStaff(thisVal);
auto& objManager = GetContext()->GetObjectManager();
auto* animObj = objManager.GetLoadedObject<PeepAnimationsObject>(peep->AnimationObjectIndex);
@@ -388,230 +414,223 @@ namespace OpenRCT2::Scripting
peep->Invalidate();
peep->UpdateSpriteBoundingBox();
peep->Invalidate();
return JS_UNDEFINED;
}
uint8_t ScStaff::animationLength_get() const
JSValue ScStaff::animationLength_get(JSContext* ctx, JSValue thisVal)
{
auto* peep = GetStaff();
auto* peep = GetStaff(thisVal);
if (peep == nullptr)
{
return 0;
return JS_NewUint32(ctx, 0);
}
auto& objManager = GetContext()->GetObjectManager();
auto* animObj = objManager.GetLoadedObject<PeepAnimationsObject>(peep->AnimationObjectIndex);
const auto& animationGroup = animObj->GetPeepAnimation(peep->AnimationGroup, peep->AnimationType);
return static_cast<uint8_t>(animationGroup.frameOffsets.size());
auto length = static_cast<uint8_t>(animationGroup.frameOffsets.size());
return JS_NewUint32(ctx, length);
}
ScHandyman::ScHandyman(EntityId Id)
: ScStaff(Id)
JSValue ScHandyman::New(JSContext* ctx, EntityId entityId)
{
JSValue obj = ScStaff::New(ctx, entityId);
AddFuncs(ctx, obj);
return obj;
}
void ScHandyman::Register(duk_context* ctx)
void ScHandyman::AddFuncs(JSContext* ctx, JSValue obj)
{
dukglue_set_base_class<ScStaff, ScHandyman>(ctx);
dukglue_register_property(ctx, &ScHandyman::lawnsMown_get, nullptr, "lawnsMown");
dukglue_register_property(ctx, &ScHandyman::gardensWatered_get, nullptr, "gardensWatered");
dukglue_register_property(ctx, &ScHandyman::litterSwept_get, nullptr, "litterSwept");
dukglue_register_property(ctx, &ScHandyman::binsEmptied_get, nullptr, "binsEmptied");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("lawnsMown", &ScHandyman::lawnsMown_get, nullptr),
JS_CGETSET_DEF("gardensWatered", &ScHandyman::gardensWatered_get, nullptr),
JS_CGETSET_DEF("litterSwept", &ScHandyman::litterSwept_get, nullptr),
JS_CGETSET_DEF("binsEmptied", &ScHandyman::binsEmptied_get, nullptr),
};
JS_SetPropertyFunctionList(ctx, obj, funcs, std::size(funcs));
}
Staff* ScHandyman::GetHandyman() const
JSValue ScHandyman::lawnsMown_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().entities.GetEntity<Staff>(_id);
}
DukValue ScHandyman::lawnsMown_get() const
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto* ctx = scriptEngine.GetContext();
auto peep = GetHandyman();
auto peep = GetStaff(thisVal);
if (peep != nullptr && peep->AssignedStaffType == StaffType::handyman)
{
duk_push_uint(ctx, peep->StaffLawnsMown);
return JS_NewUint32(ctx, peep->StaffLawnsMown);
}
else
{
duk_push_null(ctx);
return JS_NULL;
}
return DukValue::take_from_stack(ctx);
}
DukValue ScHandyman::gardensWatered_get() const
JSValue ScHandyman::gardensWatered_get(JSContext* ctx, JSValue thisVal)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto* ctx = scriptEngine.GetContext();
auto peep = GetHandyman();
auto peep = GetStaff(thisVal);
if (peep != nullptr && peep->AssignedStaffType == StaffType::handyman)
{
duk_push_uint(ctx, peep->StaffGardensWatered);
return JS_NewUint32(ctx, peep->StaffGardensWatered);
}
else
{
duk_push_null(ctx);
return JS_NULL;
}
return DukValue::take_from_stack(ctx);
}
DukValue ScHandyman::litterSwept_get() const
JSValue ScHandyman::litterSwept_get(JSContext* ctx, JSValue thisVal)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto* ctx = scriptEngine.GetContext();
auto peep = GetHandyman();
auto peep = GetStaff(thisVal);
if (peep != nullptr && peep->AssignedStaffType == StaffType::handyman)
{
duk_push_uint(ctx, peep->StaffLitterSwept);
return JS_NewUint32(ctx, peep->StaffLitterSwept);
}
else
{
duk_push_null(ctx);
return JS_NULL;
}
return DukValue::take_from_stack(ctx);
}
DukValue ScHandyman::binsEmptied_get() const
JSValue ScHandyman::binsEmptied_get(JSContext* ctx, JSValue thisVal)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto* ctx = scriptEngine.GetContext();
auto peep = GetHandyman();
auto peep = GetStaff(thisVal);
if (peep != nullptr && peep->AssignedStaffType == StaffType::handyman)
{
duk_push_uint(ctx, peep->StaffBinsEmptied);
return JS_NewUint32(ctx, peep->StaffBinsEmptied);
}
else
{
duk_push_null(ctx);
return JS_NULL;
}
return DukValue::take_from_stack(ctx);
}
ScMechanic::ScMechanic(EntityId Id)
: ScStaff(Id)
JSValue ScMechanic::New(JSContext* ctx, EntityId entityId)
{
JSValue obj = ScStaff::New(ctx, entityId);
AddFuncs(ctx, obj);
return obj;
}
void ScMechanic::Register(duk_context* ctx)
void ScMechanic::AddFuncs(JSContext* ctx, JSValue obj)
{
dukglue_set_base_class<ScStaff, ScMechanic>(ctx);
dukglue_register_property(ctx, &ScMechanic::ridesFixed_get, nullptr, "ridesFixed");
dukglue_register_property(ctx, &ScMechanic::ridesInspected_get, nullptr, "ridesInspected");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("ridesFixed", &ScMechanic::ridesFixed_get, nullptr),
JS_CGETSET_DEF("ridesInspected", &ScMechanic::ridesInspected_get, nullptr),
};
JS_SetPropertyFunctionList(ctx, obj, funcs, std::size(funcs));
}
Staff* ScMechanic::GetMechanic() const
JSValue ScMechanic::ridesFixed_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().entities.GetEntity<Staff>(_id);
}
DukValue ScMechanic::ridesFixed_get() const
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto* ctx = scriptEngine.GetContext();
auto peep = GetMechanic();
auto peep = GetStaff(thisVal);
if (peep != nullptr && peep->IsMechanic())
{
duk_push_uint(ctx, peep->StaffRidesFixed);
return JS_NewUint32(ctx, peep->StaffRidesFixed);
}
else
{
duk_push_null(ctx);
return JS_NULL;
}
return DukValue::take_from_stack(ctx);
}
DukValue ScMechanic::ridesInspected_get() const
JSValue ScMechanic::ridesInspected_get(JSContext* ctx, JSValue thisVal)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto* ctx = scriptEngine.GetContext();
auto peep = GetMechanic();
auto peep = GetStaff(thisVal);
if (peep != nullptr && peep->IsMechanic())
{
duk_push_uint(ctx, peep->StaffRidesInspected);
return JS_NewUint32(ctx, peep->StaffRidesInspected);
}
else
{
duk_push_null(ctx);
return JS_NULL;
}
return DukValue::take_from_stack(ctx);
}
ScSecurity::ScSecurity(EntityId Id)
: ScStaff(Id)
JSValue ScSecurity::New(JSContext* ctx, EntityId entityId)
{
JSValue obj = ScStaff::New(ctx, entityId);
AddFuncs(ctx, obj);
return obj;
}
void ScSecurity::Register(duk_context* ctx)
void ScSecurity::AddFuncs(JSContext* ctx, JSValue obj)
{
dukglue_set_base_class<ScStaff, ScSecurity>(ctx);
dukglue_register_property(ctx, &ScSecurity::vandalsStopped_get, nullptr, "vandalsStopped");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("vandalsStopped", &ScSecurity::vandalsStopped_get, nullptr),
};
JS_SetPropertyFunctionList(ctx, obj, funcs, std::size(funcs));
}
Staff* ScSecurity::GetSecurity() const
JSValue ScSecurity::vandalsStopped_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().entities.GetEntity<Staff>(_id);
}
DukValue ScSecurity::vandalsStopped_get() const
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto* ctx = scriptEngine.GetContext();
auto peep = GetSecurity();
auto peep = GetStaff(thisVal);
if (peep != nullptr && peep->AssignedStaffType == StaffType::security)
{
duk_push_uint(ctx, peep->StaffVandalsStopped);
return JS_NewUint32(ctx, peep->StaffVandalsStopped);
}
else
{
duk_push_null(ctx);
return JS_NULL;
}
return DukValue::take_from_stack(ctx);
}
ScPatrolArea::ScPatrolArea(EntityId id)
: _staffId(id)
using OpaquePatrolAreaData = struct
{
EntityId staffId;
};
JSValue ScPatrolArea::New(JSContext* ctx, EntityId staffId)
{
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("tiles", &ScPatrolArea::tiles_get, &ScPatrolArea::tiles_set),
JS_CFUNC_DEF("clear", 0, &ScPatrolArea::clear),
JS_CFUNC_DEF("add", 1, &ScPatrolArea::add),
JS_CFUNC_DEF("remove", 1, &ScPatrolArea::remove),
JS_CFUNC_DEF("contains", 1, &ScPatrolArea::contains),
};
return MakeWithOpaque(ctx, funcs, new OpaquePatrolAreaData{ staffId });
}
void ScPatrolArea::Register(duk_context* ctx)
void ScPatrolArea::Register(JSContext* ctx)
{
dukglue_register_property(ctx, &ScPatrolArea::tiles_get, &ScPatrolArea::tiles_set, "tiles");
dukglue_register_method(ctx, &ScPatrolArea::clear, "clear");
dukglue_register_method(ctx, &ScPatrolArea::add, "add");
dukglue_register_method(ctx, &ScPatrolArea::remove, "remove");
dukglue_register_method(ctx, &ScPatrolArea::contains, "contains");
RegisterBaseStr(ctx, "PatrolArea", Finalize);
}
Staff* ScPatrolArea::GetStaff() const
void ScPatrolArea::Finalize(JSRuntime* rt, JSValue thisVal)
{
return getGameState().entities.GetEntity<Staff>(_staffId);
OpaquePatrolAreaData* data = gScPatrolArea.GetOpaque<OpaquePatrolAreaData*>(thisVal);
if (data)
delete data;
}
void ScPatrolArea::ModifyArea(const DukValue& coordsOrRange, bool value) const
Staff* ScPatrolArea::GetStaff(JSValue thisVal)
{
auto staff = GetStaff();
OpaquePatrolAreaData* data = gScPatrolArea.GetOpaque<OpaquePatrolAreaData*>(thisVal);
return getGameState().entities.GetEntity<Staff>(data->staffId);
}
void ScPatrolArea::ModifyArea(JSContext* ctx, JSValue thisVal, JSValue coordsOrRange, bool reset)
{
auto staff = GetStaff(thisVal);
if (staff != nullptr)
{
if (coordsOrRange.is_array())
if (JS_IsArray(coordsOrRange))
{
auto dukCoords = coordsOrRange.as_array();
for (const auto& dukCoord : dukCoords)
{
auto coord = FromDuk<CoordsXY>(dukCoord);
staff->SetPatrolArea(coord, value);
JSIterateArray(ctx, coordsOrRange, [staff, reset](JSContext* ctx2, JSValue v) {
auto coord = JSToCoordsXY(ctx2, v);
staff->SetPatrolArea(coord, reset);
MapInvalidateTileFull(coord);
}
});
}
else
{
auto mapRange = FromDuk<MapRange>(coordsOrRange);
MapRange mapRange = { JSToCoordXY(ctx, coordsOrRange, "leftTop"),
JSToCoordXY(ctx, coordsOrRange, "rightBottom") };
for (int32_t y = mapRange.GetY1(); y <= mapRange.GetY2(); y += kCoordsXYStep)
{
for (int32_t x = mapRange.GetX1(); x <= mapRange.GetX2(); x += kCoordsXYStep)
{
CoordsXY coord(x, y);
staff->SetPatrolArea(coord, value);
staff->SetPatrolArea(coord, reset);
MapInvalidateTileFull(coord);
}
}
@@ -620,78 +639,82 @@ namespace OpenRCT2::Scripting
}
}
DukValue ScPatrolArea::tiles_get() const
JSValue ScPatrolArea::tiles_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto array = JS_NewArray(ctx);
duk_push_array(ctx);
auto staff = GetStaff();
auto staff = GetStaff(thisVal);
if (staff != nullptr && staff->PatrolInfo != nullptr)
{
auto tiles = staff->PatrolInfo->ToVector();
duk_uarridx_t index = 0;
auto index = 0;
for (const auto& tile : tiles)
{
auto dukCoord = ToDuk(ctx, tile.ToCoordsXY());
dukCoord.push();
duk_put_prop_index(ctx, -2, index);
auto coords = ToJSValue(ctx, tile);
JS_SetPropertyInt64(ctx, array, index, coords);
index++;
}
}
return DukValue::take_from_stack(ctx, -1);
return array;
}
void ScPatrolArea::tiles_set(const DukValue& value)
JSValue ScPatrolArea::tiles_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto staff = GetStaff();
auto staff = GetStaff(thisVal);
if (staff != nullptr)
{
staff->ClearPatrolArea();
if (value.is_array())
if (JS_IsArray(value))
{
ModifyArea(value, true);
ModifyArea(ctx, thisVal, value, true);
}
}
return JS_UNDEFINED;
}
void ScPatrolArea::clear()
JSValue ScPatrolArea::clear(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
ThrowIfGameStateNotMutable();
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto staff = GetStaff();
auto staff = GetStaff(thisVal);
if (staff != nullptr)
{
staff->ClearPatrolArea();
UpdateConsolidatedPatrolAreas();
}
return JS_UNDEFINED;
}
void ScPatrolArea::add(const DukValue& coordsOrRange)
JSValue ScPatrolArea::add(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
ThrowIfGameStateNotMutable();
ModifyArea(coordsOrRange, true);
JS_UNPACK_OBJECT(coordsOrRange, ctx, argv[0])
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
ModifyArea(ctx, thisVal, coordsOrRange, true);
return JS_UNDEFINED;
}
void ScPatrolArea::remove(const DukValue& coordsOrRange)
JSValue ScPatrolArea::remove(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
ThrowIfGameStateNotMutable();
ModifyArea(coordsOrRange, false);
JS_UNPACK_OBJECT(coordsOrRange, ctx, argv[0])
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
ModifyArea(ctx, thisVal, coordsOrRange, false);
return JS_UNDEFINED;
}
bool ScPatrolArea::contains(const DukValue& coord) const
JSValue ScPatrolArea::contains(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto staff = GetStaff();
JS_UNPACK_OBJECT(coord, ctx, argv[0])
auto staff = GetStaff(thisVal);
if (staff != nullptr)
{
auto pos = FromDuk<CoordsXY>(coord);
return staff->IsLocationInPatrol(pos);
auto pos = JSToCoordsXY(ctx, coord);
return JS_NewBool(ctx, staff->IsLocationInPatrol(pos));
}
return false;
return JS_NewBool(ctx, false);
}
} // namespace OpenRCT2::Scripting
@@ -23,110 +23,105 @@ namespace OpenRCT2
namespace OpenRCT2::Scripting
{
class ScPatrolArea
class ScPatrolArea;
extern ScPatrolArea gScPatrolArea;
class ScPatrolArea final : public ScBase
{
private:
EntityId _staffId;
public:
ScPatrolArea(EntityId id);
static void Register(duk_context* ctx);
JSValue New(JSContext* ctx, EntityId staffId);
void Register(JSContext* ctx);
private:
Staff* GetStaff() const;
void ModifyArea(const DukValue& coordsOrRange, bool value) const;
static void Finalize(JSRuntime* rt, JSValue thisVal);
static Staff* GetStaff(JSValue thisVal);
static void ModifyArea(JSContext* ctx, JSValue thisVal, JSValue coordsOrRange, bool reset);
DukValue tiles_get() const;
void tiles_set(const DukValue& value);
static JSValue tiles_get(JSContext* ctx, JSValue thisVal);
static JSValue tiles_set(JSContext* ctx, JSValue thisVal, JSValue value);
void clear();
void add(const DukValue& coordsOrRange);
void remove(const DukValue& coordsOrRange);
bool contains(const DukValue& coord) const;
static JSValue clear(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue add(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue remove(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue contains(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
};
class ScStaff : public ScPeep
{
public:
ScStaff(EntityId Id);
static JSValue New(JSContext* ctx, EntityId entityId);
static void Register(duk_context* ctx);
protected:
static Staff* GetStaff(JSValue thisVal);
private:
Staff* GetStaff() const;
static void AddFuncs(JSContext* ctx, JSValue obj);
std::string staffType_get() const;
void staffType_set(const std::string& value);
static JSValue staffType_get(JSContext* ctx, JSValue thisVal);
static JSValue staffType_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t colour_get() const;
void colour_set(uint8_t value);
static JSValue colour_get(JSContext* ctx, JSValue thisVal);
static JSValue colour_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
std::vector<std::string> availableCostumes_get() const;
std::vector<std::string> getCostumeStrings() const;
std::string costume_get() const;
void costume_set(const DukValue& value);
static JSValue availableCostumes_get(JSContext* ctx, JSValue thisVal);
static JSValue getCostumeStrings(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue costume_get(JSContext* ctx, JSValue thisVal);
static JSValue costume_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
std::shared_ptr<ScPatrolArea> patrolArea_get() const;
static JSValue patrolArea_get(JSContext* ctx, JSValue thisVal);
uint8_t orders_get() const;
void orders_set(uint8_t value);
static JSValue orders_get(JSContext* ctx, JSValue thisVal);
static JSValue orders_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
const DukEnumMap<PeepAnimationType>& animationsByStaffType(StaffType staffType) const;
std::vector<uint32_t> getAnimationSpriteIds(std::string groupKey, uint8_t rotation) const;
std::vector<std::string> availableAnimations_get() const;
std::string animation_get() const;
void animation_set(std::string groupKey);
uint8_t animationOffset_get() const;
void animationOffset_set(uint8_t offset);
uint8_t animationLength_get() const;
static EnumMap<PeepAnimationType> animationsByStaffType(StaffType staffType);
static JSValue getAnimationSpriteIds(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue availableAnimations_get(JSContext* ctx, JSValue thisVal);
static JSValue animation_get(JSContext* ctx, JSValue thisVal);
static JSValue animation_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
static JSValue animationOffset_get(JSContext* ctx, JSValue thisVal);
static JSValue animationOffset_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
static JSValue animationLength_get(JSContext* ctx, JSValue thisVal);
};
class ScHandyman : public ScStaff
class ScHandyman final : public ScStaff
{
public:
ScHandyman(EntityId Id);
static void Register(duk_context* ctx);
static JSValue New(JSContext* ctx, EntityId entityId);
private:
Staff* GetHandyman() const;
static void AddFuncs(JSContext* ctx, JSValue obj);
DukValue lawnsMown_get() const;
static JSValue lawnsMown_get(JSContext* ctx, JSValue thisVal);
DukValue gardensWatered_get() const;
static JSValue gardensWatered_get(JSContext* ctx, JSValue thisVal);
DukValue litterSwept_get() const;
static JSValue litterSwept_get(JSContext* ctx, JSValue thisVal);
DukValue binsEmptied_get() const;
static JSValue binsEmptied_get(JSContext* ctx, JSValue thisVal);
};
class ScMechanic : public ScStaff
class ScMechanic final : public ScStaff
{
public:
ScMechanic(EntityId Id);
static void Register(duk_context* ctx);
static JSValue New(JSContext* ctx, EntityId entityId);
private:
Staff* GetMechanic() const;
static void AddFuncs(JSContext* ctx, JSValue obj);
DukValue ridesFixed_get() const;
static JSValue ridesFixed_get(JSContext* ctx, JSValue thisVal);
DukValue ridesInspected_get() const;
static JSValue ridesInspected_get(JSContext* ctx, JSValue thisVal);
};
class ScSecurity : public ScStaff
class ScSecurity final : public ScStaff
{
public:
ScSecurity(EntityId Id);
static void Register(duk_context* ctx);
static JSValue New(JSContext* ctx, EntityId entityId);
private:
Staff* GetSecurity() const;
static void AddFuncs(JSContext* ctx, JSValue obj);
DukValue vandalsStopped_get() const;
static JSValue vandalsStopped_get(JSContext* ctx, JSValue thisVal);
};
} // namespace OpenRCT2::Scripting
@@ -9,6 +9,7 @@
#include "ScVehicle.hpp"
#include "../../../core/EnumMap.hpp"
#include "../../../ride/Track.h"
#include "../../../ride/TrackData.h"
#include "../../../ride/ted/TrackElementDescriptor.h"
@@ -23,7 +24,7 @@ using namespace OpenRCT2::TrackMetadata;
namespace OpenRCT2::Scripting
{
static const DukEnumMap<Vehicle::Status> VehicleStatusMap(
static const EnumMap<Vehicle::Status> VehicleStatusMap(
{
{ "moving_to_end_of_station", Vehicle::Status::movingToEndOfStation },
{ "waiting_for_passengers", Vehicle::Status::waitingForPassengers },
@@ -58,312 +59,337 @@ namespace OpenRCT2::Scripting
{ "stopped_by_block_brake", Vehicle::Status::stoppedByBlockBrakes },
});
ScVehicle::ScVehicle(EntityId id)
: ScEntity(id)
JSValue ScVehicle::New(JSContext* ctx, EntityId entityId)
{
JSValue obj = gScEntity.New(ctx, entityId);
AddFuncs(ctx, obj);
return obj;
}
void ScVehicle::Register(duk_context* ctx)
void ScVehicle::AddFuncs(JSContext* ctx, JSValue obj)
{
dukglue_set_base_class<ScEntity, ScVehicle>(ctx);
dukglue_register_property(ctx, &ScVehicle::ride_get, &ScVehicle::ride_set, "ride");
dukglue_register_property(ctx, &ScVehicle::rideObject_get, &ScVehicle::rideObject_set, "rideObject");
dukglue_register_property(ctx, &ScVehicle::vehicleObject_get, &ScVehicle::vehicleObject_set, "vehicleObject");
dukglue_register_property(ctx, &ScVehicle::spriteType_get, &ScVehicle::spriteType_set, "spriteType");
dukglue_register_property(ctx, &ScVehicle::numSeats_get, &ScVehicle::numSeats_set, "numSeats");
dukglue_register_property(ctx, &ScVehicle::nextCarOnTrain_get, &ScVehicle::nextCarOnTrain_set, "nextCarOnTrain");
dukglue_register_property(
ctx, &ScVehicle::previousCarOnRide_get, &ScVehicle::previousCarOnRide_set, "previousCarOnRide");
dukglue_register_property(ctx, &ScVehicle::nextCarOnRide_get, &ScVehicle::nextCarOnRide_set, "nextCarOnRide");
dukglue_register_property(ctx, &ScVehicle::currentStation_get, &ScVehicle::currentStation_set, "currentStation");
dukglue_register_property(ctx, &ScVehicle::mass_get, &ScVehicle::mass_set, "mass");
dukglue_register_property(ctx, &ScVehicle::acceleration_get, &ScVehicle::acceleration_set, "acceleration");
dukglue_register_property(ctx, &ScVehicle::velocity_get, &ScVehicle::velocity_set, "velocity");
dukglue_register_property(ctx, &ScVehicle::bankRotation_get, &ScVehicle::bankRotation_set, "bankRotation");
dukglue_register_property(
ctx, &ScVehicle::flag_get<VehicleFlag::carIsReversed>, &ScVehicle::flag_set<VehicleFlag::carIsReversed>,
"isReversed");
dukglue_register_property(
ctx, &ScVehicle::flag_get<VehicleFlag::crashed>, &ScVehicle::flag_set<VehicleFlag::crashed>, "isCrashed");
dukglue_register_property(ctx, &ScVehicle::colours_get, &ScVehicle::colours_set, "colours");
dukglue_register_property(ctx, &ScVehicle::trackLocation_get, nullptr, "trackLocation");
dukglue_register_property(ctx, &ScVehicle::trackProgress_get, nullptr, "trackProgress");
dukglue_register_property(ctx, &ScVehicle::remainingDistance_get, nullptr, "remainingDistance");
dukglue_register_property(ctx, &ScVehicle::subposition_get, nullptr, "subposition");
dukglue_register_property(
ctx, &ScVehicle::poweredAcceleration_get, &ScVehicle::poweredAcceleration_set, "poweredAcceleration");
dukglue_register_property(ctx, &ScVehicle::poweredMaxSpeed_get, &ScVehicle::poweredMaxSpeed_set, "poweredMaxSpeed");
dukglue_register_property(ctx, &ScVehicle::status_get, &ScVehicle::status_set, "status");
dukglue_register_property(ctx, &ScVehicle::spin_get, &ScVehicle::spin_set, "spin");
dukglue_register_property(ctx, &ScVehicle::guests_get, nullptr, "peeps");
dukglue_register_property(ctx, &ScVehicle::guests_get, nullptr, "guests");
dukglue_register_property(ctx, &ScVehicle::gForces_get, nullptr, "gForces");
dukglue_register_method(ctx, &ScVehicle::travelBy, "travelBy");
dukglue_register_method(ctx, &ScVehicle::moveToTrack, "moveToTrack");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("ride", &ScVehicle::ride_get, &ScVehicle::ride_set),
JS_CGETSET_DEF("rideObject", &ScVehicle::rideObject_get, &ScVehicle::rideObject_set),
JS_CGETSET_DEF("vehicleObject", &ScVehicle::vehicleObject_get, &ScVehicle::vehicleObject_set),
JS_CGETSET_DEF("spriteType", &ScVehicle::spriteType_get, &ScVehicle::spriteType_set),
JS_CGETSET_DEF("numSeats", &ScVehicle::numSeats_get, &ScVehicle::numSeats_set),
JS_CGETSET_DEF("nextCarOnTrain", &ScVehicle::nextCarOnTrain_get, &ScVehicle::nextCarOnTrain_set),
JS_CGETSET_DEF("previousCarOnRide", &ScVehicle::previousCarOnRide_get, &ScVehicle::previousCarOnRide_set),
JS_CGETSET_DEF("nextCarOnRide", &ScVehicle::nextCarOnRide_get, &ScVehicle::nextCarOnRide_set),
JS_CGETSET_DEF("currentStation", &ScVehicle::currentStation_get, &ScVehicle::currentStation_set),
JS_CGETSET_DEF("mass", &ScVehicle::mass_get, &ScVehicle::mass_set),
JS_CGETSET_DEF("acceleration", &ScVehicle::acceleration_get, &ScVehicle::acceleration_set),
JS_CGETSET_DEF("velocity", &ScVehicle::velocity_get, &ScVehicle::velocity_set),
JS_CGETSET_DEF("bankRotation", &ScVehicle::bankRotation_get, &ScVehicle::bankRotation_set),
JS_CGETSET_DEF(
"isReversed", &ScVehicle::flag_get<VehicleFlag::carIsReversed>,
&ScVehicle::flag_set<VehicleFlag::carIsReversed>),
JS_CGETSET_DEF("isCrashed", &ScVehicle::flag_get<VehicleFlag::crashed>, &ScVehicle::flag_set<VehicleFlag::crashed>),
JS_CGETSET_DEF("colours", &ScVehicle::colours_get, &ScVehicle::colours_set),
JS_CGETSET_DEF("trackLocation", &ScVehicle::trackLocation_get, nullptr),
JS_CGETSET_DEF("trackProgress", &ScVehicle::trackProgress_get, nullptr),
JS_CGETSET_DEF("remainingDistance", &ScVehicle::remainingDistance_get, nullptr),
JS_CGETSET_DEF("subposition", &ScVehicle::subposition_get, nullptr),
JS_CGETSET_DEF("poweredAcceleration", &ScVehicle::poweredAcceleration_get, &ScVehicle::poweredAcceleration_set),
JS_CGETSET_DEF("poweredMaxSpeed", &ScVehicle::poweredMaxSpeed_get, &ScVehicle::poweredMaxSpeed_set),
JS_CGETSET_DEF("status", &ScVehicle::status_get, &ScVehicle::status_set),
JS_CGETSET_DEF("spin", &ScVehicle::spin_get, &ScVehicle::spin_set),
JS_CGETSET_DEF("peeps", &ScVehicle::guests_get, nullptr),
JS_CGETSET_DEF("guests", &ScVehicle::guests_get, nullptr),
JS_CGETSET_DEF("gForces", &ScVehicle::gForces_get, nullptr),
JS_CFUNC_DEF("travelBy", 1, &ScVehicle::travelBy),
JS_CFUNC_DEF("moveToTrack", 3, &ScVehicle::moveToTrack),
};
JS_SetPropertyFunctionList(ctx, obj, funcs, std::size(funcs));
}
Vehicle* ScVehicle::GetVehicle() const
Vehicle* ScVehicle::GetVehicle(JSValue thisVal)
{
return getGameState().entities.GetEntity<Vehicle>(_id);
auto id = GetEntityId(thisVal);
return getGameState().entities.GetEntity<Vehicle>(id);
}
ObjectEntryIndex ScVehicle::rideObject_get() const
JSValue ScVehicle::rideObject_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->ride_subtype : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? vehicle->ride_subtype : 0);
}
void ScVehicle::rideObject_set(ObjectEntryIndex value)
JSValue ScVehicle::rideObject_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->ride_subtype = value;
vehicle->Invalidate();
}
return JS_UNDEFINED;
}
uint8_t ScVehicle::vehicleObject_get() const
JSValue ScVehicle::vehicleObject_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->vehicle_type : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? vehicle->vehicle_type : 0);
}
void ScVehicle::vehicleObject_set(uint8_t value)
JSValue ScVehicle::vehicleObject_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->vehicle_type = value;
vehicle->Invalidate();
}
return JS_UNDEFINED;
}
uint8_t ScVehicle::spriteType_get() const
JSValue ScVehicle::spriteType_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? EnumValue(vehicle->pitch) : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? EnumValue(vehicle->pitch) : 0);
}
void ScVehicle::spriteType_set(uint8_t value)
JSValue ScVehicle::spriteType_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->pitch = static_cast<VehiclePitch>(value);
vehicle->Invalidate();
}
return JS_UNDEFINED;
}
int32_t ScVehicle::ride_get() const
JSValue ScVehicle::ride_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return (vehicle != nullptr ? vehicle->ride : RideId::GetNull()).ToUnderlying();
auto vehicle = GetVehicle(thisVal);
auto rideId = vehicle != nullptr ? vehicle->ride : RideId::GetNull();
return JS_NewUint32(ctx, rideId.ToUnderlying());
}
void ScVehicle::ride_set(int32_t value)
JSValue ScVehicle::ride_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_INT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->ride = RideId::FromUnderlying(value);
}
return JS_UNDEFINED;
}
uint8_t ScVehicle::numSeats_get() const
JSValue ScVehicle::numSeats_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->num_seats & kVehicleSeatNumMask : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? vehicle->num_seats & kVehicleSeatNumMask : 0);
}
void ScVehicle::numSeats_set(uint8_t value)
JSValue ScVehicle::numSeats_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->num_seats &= ~kVehicleSeatNumMask;
vehicle->num_seats |= value & kVehicleSeatNumMask;
}
return JS_UNDEFINED;
}
DukValue ScVehicle::nextCarOnTrain_get() const
JSValue ScVehicle::nextCarOnTrain_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto vehicle = GetVehicle();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
if (!vehicle->next_vehicle_on_train.IsNull())
{
return ToDuk<int32_t>(ctx, vehicle->next_vehicle_on_train.ToUnderlying());
return JS_NewUint32(ctx, vehicle->next_vehicle_on_train.ToUnderlying());
}
}
return ToDuk(ctx, nullptr);
return JS_NULL;
}
void ScVehicle::nextCarOnTrain_set(DukValue value)
JSValue ScVehicle::nextCarOnTrain_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
if (value.type() == DukValue::Type::NUMBER)
{
vehicle->next_vehicle_on_train = EntityId::FromUnderlying(value.as_uint());
}
else
if (JS_IsNull(jsValue))
{
vehicle->next_vehicle_on_train = EntityId::GetNull();
}
else
{
JS_UNPACK_UINT32(entityId, ctx, jsValue);
vehicle->next_vehicle_on_train = EntityId::FromUnderlying(entityId);
}
}
return JS_UNDEFINED;
}
DukValue ScVehicle::previousCarOnRide_get() const
JSValue ScVehicle::previousCarOnRide_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
const auto* vehicle = GetVehicle();
const auto* vehicle = GetVehicle(thisVal);
if (vehicle == nullptr)
return ToDuk(ctx, nullptr);
return JS_NULL;
if (vehicle->prev_vehicle_on_ride.IsNull())
return ToDuk(ctx, nullptr);
return JS_NULL;
return ToDuk(ctx, vehicle->prev_vehicle_on_ride.ToUnderlying());
return JS_NewUint32(ctx, vehicle->prev_vehicle_on_ride.ToUnderlying());
}
void ScVehicle::previousCarOnRide_set(DukValue value)
JSValue ScVehicle::previousCarOnRide_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto* vehicle = GetVehicle();
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto* vehicle = GetVehicle(thisVal);
if (vehicle == nullptr)
return;
return JS_UNDEFINED;
if (value.type() == DukValue::Type::NUMBER)
{
vehicle->prev_vehicle_on_ride = EntityId::FromUnderlying(value.as_uint());
}
else
if (JS_IsNull(jsValue))
{
vehicle->prev_vehicle_on_ride = EntityId::GetNull();
}
else
{
JS_UNPACK_UINT32(entityId, ctx, jsValue);
vehicle->prev_vehicle_on_ride = EntityId::FromUnderlying(entityId);
}
return JS_UNDEFINED;
}
DukValue ScVehicle::nextCarOnRide_get() const
JSValue ScVehicle::nextCarOnRide_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
const auto* vehicle = GetVehicle();
const auto* vehicle = GetVehicle(thisVal);
if (vehicle == nullptr)
return ToDuk(ctx, nullptr);
return JS_NULL;
if (vehicle->next_vehicle_on_ride.IsNull())
return ToDuk(ctx, nullptr);
return JS_NULL;
return ToDuk(ctx, vehicle->next_vehicle_on_ride.ToUnderlying());
return JS_NewUint32(ctx, vehicle->next_vehicle_on_ride.ToUnderlying());
}
void ScVehicle::nextCarOnRide_set(DukValue value)
JSValue ScVehicle::nextCarOnRide_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto* vehicle = GetVehicle();
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto* vehicle = GetVehicle(thisVal);
if (vehicle == nullptr)
return;
return JS_UNDEFINED;
if (value.type() == DukValue::Type::NUMBER)
{
vehicle->next_vehicle_on_ride = EntityId::FromUnderlying(value.as_uint());
}
else
if (JS_IsNull(jsValue))
{
vehicle->next_vehicle_on_ride = EntityId::GetNull();
}
else
{
JS_UNPACK_UINT32(entityId, ctx, jsValue);
vehicle->next_vehicle_on_ride = EntityId::FromUnderlying(entityId);
}
return JS_UNDEFINED;
}
StationIndex::UnderlyingType ScVehicle::currentStation_get() const
JSValue ScVehicle::currentStation_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->current_station.ToUnderlying() : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? vehicle->current_station.ToUnderlying() : 0);
}
void ScVehicle::currentStation_set(StationIndex::UnderlyingType value)
JSValue ScVehicle::currentStation_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->current_station = StationIndex::FromUnderlying(value);
}
return JS_UNDEFINED;
}
uint16_t ScVehicle::mass_get() const
JSValue ScVehicle::mass_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->mass : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? vehicle->mass : 0);
}
void ScVehicle::mass_set(uint16_t value)
JSValue ScVehicle::mass_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->mass = value;
}
return JS_UNDEFINED;
}
int32_t ScVehicle::acceleration_get() const
JSValue ScVehicle::acceleration_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->acceleration : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewInt32(ctx, vehicle != nullptr ? vehicle->acceleration : 0);
}
void ScVehicle::acceleration_set(int32_t value)
JSValue ScVehicle::acceleration_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_INT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->acceleration = value;
}
return JS_UNDEFINED;
}
int32_t ScVehicle::velocity_get() const
JSValue ScVehicle::velocity_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->velocity : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewInt32(ctx, vehicle != nullptr ? vehicle->velocity : 0);
}
void ScVehicle::velocity_set(int32_t value)
JSValue ScVehicle::velocity_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_INT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->velocity = value;
}
return JS_UNDEFINED;
}
uint8_t ScVehicle::bankRotation_get() const
JSValue ScVehicle::bankRotation_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? EnumValue(vehicle->roll) : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? EnumValue(vehicle->roll) : 0);
}
void ScVehicle::bankRotation_set(uint8_t value)
JSValue ScVehicle::bankRotation_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->roll = static_cast<VehicleRoll>(value);
vehicle->Invalidate();
}
return JS_UNDEFINED;
}
template<VehicleFlag flag>
bool ScVehicle::flag_get() const
JSValue ScVehicle::flag_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->flags.has(static_cast<VehicleFlag>(flag)) : false;
auto vehicle = GetVehicle(thisVal);
return JS_NewBool(ctx, vehicle != nullptr ? vehicle->flags.has(static_cast<VehicleFlag>(flag)) : false);
}
template<VehicleFlag flag>
void ScVehicle::flag_set(bool value)
JSValue ScVehicle::flag_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_BOOL(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
if (value)
@@ -376,196 +402,209 @@ namespace OpenRCT2::Scripting
}
vehicle->Invalidate();
}
return JS_UNDEFINED;
}
DukValue ScVehicle::colours_get() const
JSValue ScVehicle::colours_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto vehicle = GetVehicle();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
return ToDuk<VehicleColour>(ctx, vehicle->colours);
return ToJSValue(ctx, vehicle->colours);
}
return ToDuk(ctx, nullptr);
return JS_NULL;
}
void ScVehicle::colours_set(const DukValue& value)
JSValue ScVehicle::colours_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_OBJECT(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->colours = FromDuk<VehicleColour>(value);
vehicle->colours = JSToVehicleColours(ctx, value);
vehicle->Invalidate();
}
return JS_UNDEFINED;
}
DukValue ScVehicle::trackLocation_get() const
JSValue ScVehicle::trackLocation_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto vehicle = GetVehicle();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
DukObject dukCoords(ctx);
dukCoords.Set("x", vehicle->TrackLocation.x);
dukCoords.Set("y", vehicle->TrackLocation.y);
dukCoords.Set("z", vehicle->TrackLocation.z);
dukCoords.Set("direction", vehicle->GetTrackDirection());
dukCoords.Set("trackType", EnumValue(vehicle->GetTrackType()));
return dukCoords.Take();
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, vehicle->TrackLocation.x));
JS_SetPropertyStr(ctx, obj, "y", JS_NewInt32(ctx, vehicle->TrackLocation.y));
JS_SetPropertyStr(ctx, obj, "z", JS_NewInt32(ctx, vehicle->TrackLocation.z));
JS_SetPropertyStr(ctx, obj, "direction", JS_NewUint32(ctx, vehicle->GetTrackDirection()));
JS_SetPropertyStr(ctx, obj, "trackType", JS_NewUint32(ctx, EnumValue(vehicle->GetTrackType())));
return obj;
}
return ToDuk(ctx, nullptr);
return JS_NULL;
}
uint16_t ScVehicle::trackProgress_get() const
JSValue ScVehicle::trackProgress_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->track_progress : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? vehicle->track_progress : 0);
}
int32_t ScVehicle::remainingDistance_get() const
JSValue ScVehicle::remainingDistance_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->remaining_distance : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewInt32(ctx, vehicle != nullptr ? vehicle->remaining_distance : 0);
}
uint8_t ScVehicle::subposition_get() const
JSValue ScVehicle::subposition_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? static_cast<uint8_t>(vehicle->TrackSubposition) : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? static_cast<uint8_t>(vehicle->TrackSubposition) : 0);
}
uint8_t ScVehicle::poweredAcceleration_get() const
JSValue ScVehicle::poweredAcceleration_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->powered_acceleration : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? vehicle->powered_acceleration : 0);
}
void ScVehicle::poweredAcceleration_set(uint8_t value)
JSValue ScVehicle::poweredAcceleration_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->powered_acceleration = value;
}
return JS_UNDEFINED;
}
uint8_t ScVehicle::poweredMaxSpeed_get() const
JSValue ScVehicle::poweredMaxSpeed_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
return vehicle != nullptr ? vehicle->speed : 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? vehicle->speed : 0);
}
void ScVehicle::poweredMaxSpeed_set(uint8_t value)
JSValue ScVehicle::poweredMaxSpeed_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->speed = value;
}
return JS_UNDEFINED;
}
std::string ScVehicle::status_get() const
JSValue ScVehicle::status_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
return std::string(VehicleStatusMap[vehicle->status]);
return JSFromStdString(ctx, VehicleStatusMap[vehicle->status]);
}
return {};
return JS_UNDEFINED;
}
void ScVehicle::status_set(const std::string& value)
JSValue ScVehicle::status_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_STR(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->status = VehicleStatusMap[value];
}
return JS_UNDEFINED;
}
uint8_t ScVehicle::spin_get() const
JSValue ScVehicle::spin_get(JSContext* ctx, JSValue thisVal)
{
auto vehicle = GetVehicle();
if (vehicle != nullptr)
{
return vehicle->spin_sprite;
}
return 0;
auto vehicle = GetVehicle(thisVal);
return JS_NewUint32(ctx, vehicle != nullptr ? vehicle->spin_sprite : 0);
}
void ScVehicle::spin_set(const uint8_t value)
JSValue ScVehicle::spin_set(JSContext* ctx, JSValue thisVal, JSValue jsValue)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_UINT32(value, ctx, jsValue);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->spin_sprite = value;
vehicle->Invalidate();
}
return JS_UNDEFINED;
}
std::vector<DukValue> ScVehicle::guests_get() const
JSValue ScVehicle::guests_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
std::vector<DukValue> result;
auto vehicle = GetVehicle();
auto result = JS_NewArray(ctx);
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
size_t len = 0;
for (size_t i = 0; i < std::size(vehicle->peep); i++)
{
auto peep = vehicle->peep[i];
if (peep.IsNull())
if (!peep.IsNull())
{
result.push_back(ToDuk(ctx, nullptr));
}
else
{
result.push_back(ToDuk<int32_t>(ctx, peep.ToUnderlying()));
// Set all peep slots between last valid peep and current to NULL (if there were any null peeps).
for (size_t j = len; j < i; j++)
{
JS_SetPropertyInt64(ctx, result, i, JS_NULL);
}
JS_SetPropertyInt64(ctx, result, i, JS_NewUint32(ctx, peep.ToUnderlying()));
len = i + 1;
}
}
result.resize(len);
}
return result;
}
DukValue ScVehicle::gForces_get() const
JSValue ScVehicle::gForces_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto vehicle = GetVehicle();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
GForces gForces = vehicle->GetGForces();
return ToDuk<GForces>(ctx, gForces);
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "lateralG", JS_NewInt32(ctx, gForces.lateralG));
JS_SetPropertyStr(ctx, obj, "verticalG", JS_NewInt32(ctx, gForces.verticalG));
return obj;
}
return ToDuk(ctx, nullptr);
return JS_NULL;
}
void ScVehicle::travelBy(int32_t value)
JSValue ScVehicle::travelBy(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
ThrowIfGameStateNotMutable();
auto vehicle = GetVehicle();
JS_UNPACK_INT32(value, ctx, argv[0]);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle != nullptr)
{
vehicle->MoveRelativeDistance(value);
EntityTweener::Get().RemoveEntity(vehicle);
}
return JS_UNDEFINED;
}
void ScVehicle::moveToTrack(int32_t x, int32_t y, int32_t elementIndex)
JSValue ScVehicle::moveToTrack(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto vehicle = GetVehicle();
JS_UNPACK_INT32(x, ctx, argv[0]);
JS_UNPACK_INT32(y, ctx, argv[1]);
JS_UNPACK_INT32(elementIndex, ctx, argv[2]);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto vehicle = GetVehicle(thisVal);
if (vehicle == nullptr)
return;
return JS_UNDEFINED;
CoordsXY coords = TileCoordsXY(x, y).ToCoordsXY();
auto el = MapGetNthElementAt(coords, elementIndex);
if (el == nullptr)
return;
return JS_UNDEFINED;
auto origin = GetTrackSegmentOrigin(CoordsXYE(coords, el));
if (!origin)
return;
return JS_UNDEFINED;
const auto& trackType = el->AsTrack()->GetTrackType();
const auto& ted = GetTrackElementDescriptor(trackType);
@@ -585,6 +624,7 @@ namespace OpenRCT2::Scripting
vehicle->UpdateTrackChange();
EntityTweener::Get().RemoveEntity(vehicle);
return JS_UNDEFINED;
}
} // namespace OpenRCT2::Scripting
@@ -13,97 +13,96 @@
#include "../../../entity/EntityTweener.h"
#include "../../../ride/Ride.h"
#include "../../../ride/Vehicle.h"
#include "ScEntity.hpp"
#include <optional>
namespace OpenRCT2::Scripting
{
class ScVehicle : public ScEntity
class ScVehicle final : public ScEntity
{
public:
ScVehicle(EntityId id);
static void Register(duk_context* ctx);
static JSValue New(JSContext* ctx, EntityId entityId);
private:
Vehicle* GetVehicle() const;
static void AddFuncs(JSContext* ctx, JSValue obj);
static Vehicle* GetVehicle(JSValue thisVal);
ObjectEntryIndex rideObject_get() const;
void rideObject_set(ObjectEntryIndex value);
static JSValue rideObject_get(JSContext* ctx, JSValue thisVal);
static JSValue rideObject_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t vehicleObject_get() const;
void vehicleObject_set(uint8_t value);
static JSValue vehicleObject_get(JSContext* ctx, JSValue thisVal);
static JSValue vehicleObject_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t spriteType_get() const;
void spriteType_set(uint8_t value);
static JSValue spriteType_get(JSContext* ctx, JSValue thisVal);
static JSValue spriteType_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
int32_t ride_get() const;
void ride_set(int32_t value);
static JSValue ride_get(JSContext* ctx, JSValue thisVal);
static JSValue ride_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t numSeats_get() const;
void numSeats_set(uint8_t value);
static JSValue numSeats_get(JSContext* ctx, JSValue thisVal);
static JSValue numSeats_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
DukValue nextCarOnTrain_get() const;
void nextCarOnTrain_set(DukValue value);
static JSValue nextCarOnTrain_get(JSContext* ctx, JSValue thisVal);
static JSValue nextCarOnTrain_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
DukValue previousCarOnRide_get() const;
void previousCarOnRide_set(DukValue value);
static JSValue previousCarOnRide_get(JSContext* ctx, JSValue thisVal);
static JSValue previousCarOnRide_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
DukValue nextCarOnRide_get() const;
void nextCarOnRide_set(DukValue value);
static JSValue nextCarOnRide_get(JSContext* ctx, JSValue thisVal);
static JSValue nextCarOnRide_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
StationIndex::UnderlyingType currentStation_get() const;
void currentStation_set(StationIndex::UnderlyingType value);
static JSValue currentStation_get(JSContext* ctx, JSValue thisVal);
static JSValue currentStation_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint16_t mass_get() const;
void mass_set(uint16_t value);
static JSValue mass_get(JSContext* ctx, JSValue thisVal);
static JSValue mass_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
int32_t acceleration_get() const;
void acceleration_set(int32_t value);
static JSValue acceleration_get(JSContext* ctx, JSValue thisVal);
static JSValue acceleration_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
int32_t velocity_get() const;
void velocity_set(int32_t value);
static JSValue velocity_get(JSContext* ctx, JSValue thisVal);
static JSValue velocity_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t bankRotation_get() const;
void bankRotation_set(uint8_t value);
static JSValue bankRotation_get(JSContext* ctx, JSValue thisVal);
static JSValue bankRotation_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
template<VehicleFlag flag>
bool flag_get() const;
static JSValue flag_get(JSContext* ctx, JSValue thisVal);
template<VehicleFlag flag>
void flag_set(bool value);
static JSValue flag_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
DukValue colours_get() const;
void colours_set(const DukValue& value);
static JSValue colours_get(JSContext* ctx, JSValue thisVal);
static JSValue colours_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
DukValue trackLocation_get() const;
void trackLocation_set(const DukValue& value);
static JSValue trackLocation_get(JSContext* ctx, JSValue thisVal);
uint16_t trackProgress_get() const;
static JSValue trackProgress_get(JSContext* ctx, JSValue thisVal);
int32_t remainingDistance_get() const;
static JSValue remainingDistance_get(JSContext* ctx, JSValue thisVal);
uint8_t subposition_get() const;
static JSValue subposition_get(JSContext* ctx, JSValue thisVal);
uint8_t poweredAcceleration_get() const;
void poweredAcceleration_set(uint8_t value);
static JSValue poweredAcceleration_get(JSContext* ctx, JSValue thisVal);
static JSValue poweredAcceleration_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t poweredMaxSpeed_get() const;
void poweredMaxSpeed_set(uint8_t value);
static JSValue poweredMaxSpeed_get(JSContext* ctx, JSValue thisVal);
static JSValue poweredMaxSpeed_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
std::string status_get() const;
void status_set(const std::string& value);
static JSValue status_get(JSContext* ctx, JSValue thisVal);
static JSValue status_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
uint8_t spin_get() const;
void spin_set(uint8_t value);
static JSValue spin_get(JSContext* ctx, JSValue thisVal);
static JSValue spin_set(JSContext* ctx, JSValue thisVal, JSValue jsValue);
std::vector<DukValue> guests_get() const;
static JSValue guests_get(JSContext* ctx, JSValue thisVal);
DukValue gForces_get() const;
static JSValue gForces_get(JSContext* ctx, JSValue thisVal);
void travelBy(int32_t value);
static JSValue travelBy(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
void moveToTrack(int32_t x, int32_t y, int32_t elementIndex);
static JSValue moveToTrack(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
};
} // namespace OpenRCT2::Scripting
+278 -194
View File
@@ -14,372 +14,456 @@
#include "../../../Cheats.h"
#include "../../../GameState.h"
#include "../../../world/Park.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
namespace OpenRCT2::Scripting
{
class ScCheats
class ScCheats;
extern ScCheats gScCheats;
class ScCheats final : public ScBase
{
public:
static void Register(duk_context* ctx)
JSValue New(JSContext* ctx)
{
dukglue_register_property(
ctx, &ScCheats::allowArbitraryRideTypeChanges_get, &ScCheats::allowArbitraryRideTypeChanges_set,
"allowArbitraryRideTypeChanges");
dukglue_register_property(
ctx, &ScCheats::allowTrackPlaceInvalidHeights_get, &ScCheats::allowTrackPlaceInvalidHeights_set,
"allowTrackPlaceInvalidHeights");
dukglue_register_property(
ctx, &ScCheats::buildInPauseMode_get, &ScCheats::buildInPauseMode_set, "buildInPauseMode");
dukglue_register_property(
ctx, &ScCheats::disableAllBreakdowns_get, &ScCheats::disableAllBreakdowns_set, "disableAllBreakdowns");
dukglue_register_property(
ctx, &ScCheats::disableBrakesFailure_get, &ScCheats::disableBrakesFailure_set, "disableBrakesFailure");
dukglue_register_property(
ctx, &ScCheats::disableClearanceChecks_get, &ScCheats::disableClearanceChecks_set, "disableClearanceChecks");
dukglue_register_property(
ctx, &ScCheats::disableLittering_get, &ScCheats::disableLittering_set, "disableLittering");
dukglue_register_property(
ctx, &ScCheats::disablePlantAging_get, &ScCheats::disablePlantAging_set, "disablePlantAging");
dukglue_register_property(
ctx, &ScCheats::allowRegularPathAsQueue_get, &ScCheats::allowRegularPathAsQueue_set, "allowRegularPathAsQueue");
dukglue_register_property(
ctx, &ScCheats::allowSpecialColourSchemes_get, &ScCheats::allowSpecialColourSchemes_set,
"allowSpecialColourSchemes");
dukglue_register_property(
ctx, &ScCheats::disableRideValueAging_get, &ScCheats::disableRideValueAging_set, "disableRideValueAging");
dukglue_register_property(
ctx, &ScCheats::disableSupportLimits_get, &ScCheats::disableSupportLimits_set, "disableSupportLimits");
dukglue_register_property(
ctx, &ScCheats::disableTrainLengthLimit_get, &ScCheats::disableTrainLengthLimit_set, "disableTrainLengthLimit");
dukglue_register_property(
ctx, &ScCheats::disableVandalism_get, &ScCheats::disableVandalism_set, "disableVandalism");
dukglue_register_property(
ctx, &ScCheats::enableAllDrawableTrackPieces_get, &ScCheats::enableAllDrawableTrackPieces_set,
"enableAllDrawableTrackPieces");
dukglue_register_property(
ctx, &ScCheats::enableChainLiftOnAllTrack_get, &ScCheats::enableChainLiftOnAllTrack_set,
"enableChainLiftOnAllTrack");
dukglue_register_property(ctx, &ScCheats::fastLiftHill_get, &ScCheats::fastLiftHill_set, "fastLiftHill");
dukglue_register_property(ctx, &ScCheats::freezeWeather_get, &ScCheats::freezeWeather_set, "freezeWeather");
dukglue_register_property(
ctx, &ScCheats::ignoreResearchStatus_get, &ScCheats::ignoreResearchStatus_set, "ignoreResearchStatus");
dukglue_register_property(
ctx, &ScCheats::ignoreRideIntensity_get, &ScCheats::ignoreRideIntensity_set, "ignoreRideIntensity");
dukglue_register_property(ctx, &ScCheats::ignoreRidePrice_get, &ScCheats::ignoreRidePrice_set, "ignoreRidePrice");
dukglue_register_property(
ctx, &ScCheats::neverendingMarketing_get, &ScCheats::neverendingMarketing_set, "neverendingMarketing");
dukglue_register_property(
ctx, &ScCheats::forcedParkRating_get, &ScCheats::forcedParkRating_set, "forcedParkRating");
dukglue_register_property(ctx, &ScCheats::sandboxMode_get, &ScCheats::sandboxMode_set, "sandboxMode");
dukglue_register_property(
ctx, &ScCheats::showAllOperatingModes_get, &ScCheats::showAllOperatingModes_set, "showAllOperatingModes");
dukglue_register_property(
ctx, &ScCheats::showVehiclesFromOtherTrackTypes_get, &ScCheats::showVehiclesFromOtherTrackTypes_set,
"showVehiclesFromOtherTrackTypes");
dukglue_register_property(
ctx, &ScCheats::makeAllDestructible_get, &ScCheats::makeAllDestructible_set, "makeAllDestructible");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF(
"allowArbitraryRideTypeChanges", &ScCheats::allowArbitraryRideTypeChanges_get,
&ScCheats::allowArbitraryRideTypeChanges_set),
JS_CGETSET_DEF(
"allowTrackPlaceInvalidHeights", &ScCheats::allowTrackPlaceInvalidHeights_get,
&ScCheats::allowTrackPlaceInvalidHeights_set),
JS_CGETSET_DEF("buildInPauseMode", &ScCheats::buildInPauseMode_get, &ScCheats::buildInPauseMode_set),
JS_CGETSET_DEF(
"disableAllBreakdowns", &ScCheats::disableAllBreakdowns_get, &ScCheats::disableAllBreakdowns_set),
JS_CGETSET_DEF(
"disableBrakesFailure", &ScCheats::disableBrakesFailure_get, &ScCheats::disableBrakesFailure_set),
JS_CGETSET_DEF(
"disableClearanceChecks", &ScCheats::disableClearanceChecks_get, &ScCheats::disableClearanceChecks_set),
JS_CGETSET_DEF("disableLittering", &ScCheats::disableLittering_get, &ScCheats::disableLittering_set),
JS_CGETSET_DEF("disablePlantAging", &ScCheats::disablePlantAging_get, &ScCheats::disablePlantAging_set),
JS_CGETSET_DEF(
"allowRegularPathAsQueue", &ScCheats::allowRegularPathAsQueue_get, &ScCheats::allowRegularPathAsQueue_set),
JS_CGETSET_DEF(
"allowSpecialColourSchemes", &ScCheats::allowSpecialColourSchemes_get,
&ScCheats::allowSpecialColourSchemes_set),
JS_CGETSET_DEF(
"disableRideValueAging", &ScCheats::disableRideValueAging_get, &ScCheats::disableRideValueAging_set),
JS_CGETSET_DEF(
"disableSupportLimits", &ScCheats::disableSupportLimits_get, &ScCheats::disableSupportLimits_set),
JS_CGETSET_DEF(
"disableTrainLengthLimit", &ScCheats::disableTrainLengthLimit_get, &ScCheats::disableTrainLengthLimit_set),
JS_CGETSET_DEF("disableVandalism", &ScCheats::disableVandalism_get, &ScCheats::disableVandalism_set),
JS_CGETSET_DEF(
"enableAllDrawableTrackPieces", &ScCheats::enableAllDrawableTrackPieces_get,
&ScCheats::enableAllDrawableTrackPieces_set),
JS_CGETSET_DEF(
"enableChainLiftOnAllTrack", &ScCheats::enableChainLiftOnAllTrack_get,
&ScCheats::enableChainLiftOnAllTrack_set),
JS_CGETSET_DEF("fastLiftHill", &ScCheats::fastLiftHill_get, &ScCheats::fastLiftHill_set),
JS_CGETSET_DEF("freezeWeather", &ScCheats::freezeWeather_get, &ScCheats::freezeWeather_set),
JS_CGETSET_DEF(
"ignoreResearchStatus", &ScCheats::ignoreResearchStatus_get, &ScCheats::ignoreResearchStatus_set),
JS_CGETSET_DEF("ignoreRideIntensity", &ScCheats::ignoreRideIntensity_get, &ScCheats::ignoreRideIntensity_set),
JS_CGETSET_DEF("ignoreRidePrice", &ScCheats::ignoreRidePrice_get, &ScCheats::ignoreRidePrice_set),
JS_CGETSET_DEF(
"neverendingMarketing", &ScCheats::neverendingMarketing_get, &ScCheats::neverendingMarketing_set),
JS_CGETSET_DEF("forcedParkRating", &ScCheats::forcedParkRating_get, &ScCheats::forcedParkRating_set),
JS_CGETSET_DEF("sandboxMode", &ScCheats::sandboxMode_get, &ScCheats::sandboxMode_set),
JS_CGETSET_DEF(
"showAllOperatingModes", &ScCheats::showAllOperatingModes_get, &ScCheats::showAllOperatingModes_set),
JS_CGETSET_DEF(
"showVehiclesFromOtherTrackTypes", &ScCheats::showVehiclesFromOtherTrackTypes_get,
&ScCheats::showVehiclesFromOtherTrackTypes_set),
JS_CGETSET_DEF("makeAllDestructible", &ScCheats::makeAllDestructible_get, &ScCheats::makeAllDestructible_set)
};
return MakeWithOpaque(ctx, funcs, nullptr);
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Cheats");
}
private:
bool allowArbitraryRideTypeChanges_get()
static JSValue allowArbitraryRideTypeChanges_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.allowArbitraryRideTypeChanges;
return JS_NewBool(ctx, getGameState().cheats.allowArbitraryRideTypeChanges);
}
void allowArbitraryRideTypeChanges_set(bool value)
static JSValue allowArbitraryRideTypeChanges_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.allowArbitraryRideTypeChanges = value;
JS_UNPACK_BOOL(valueBool, ctx, value)
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.allowArbitraryRideTypeChanges = valueBool;
return JS_UNDEFINED;
}
bool allowTrackPlaceInvalidHeights_get()
static JSValue allowTrackPlaceInvalidHeights_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.allowTrackPlaceInvalidHeights;
return JS_NewBool(ctx, getGameState().cheats.allowTrackPlaceInvalidHeights);
}
void allowTrackPlaceInvalidHeights_set(bool value)
static JSValue allowTrackPlaceInvalidHeights_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.allowTrackPlaceInvalidHeights = value;
JS_UNPACK_BOOL(valueBool, ctx, value)
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.allowTrackPlaceInvalidHeights = valueBool;
return JS_UNDEFINED;
}
bool buildInPauseMode_get()
static JSValue buildInPauseMode_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.buildInPauseMode;
return JS_NewBool(ctx, getGameState().cheats.buildInPauseMode);
}
void buildInPauseMode_set(bool value)
static JSValue buildInPauseMode_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.buildInPauseMode = value;
JS_UNPACK_BOOL(valueBool, ctx, value)
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.buildInPauseMode = valueBool;
return JS_UNDEFINED;
}
bool disableAllBreakdowns_get()
static JSValue disableAllBreakdowns_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.disableAllBreakdowns;
return JS_NewBool(ctx, getGameState().cheats.disableAllBreakdowns);
}
void disableAllBreakdowns_set(bool value)
static JSValue disableAllBreakdowns_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.disableAllBreakdowns = value;
JS_UNPACK_BOOL(valueBool, ctx, value)
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.disableAllBreakdowns = valueBool;
return JS_UNDEFINED;
}
bool disableBrakesFailure_get()
static JSValue disableBrakesFailure_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.disableBrakesFailure;
return JS_NewBool(ctx, getGameState().cheats.disableBrakesFailure);
}
void disableBrakesFailure_set(bool value)
static JSValue disableBrakesFailure_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.disableBrakesFailure = value;
JS_UNPACK_BOOL(valueBool, ctx, value)
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.disableBrakesFailure = valueBool;
return JS_UNDEFINED;
}
bool disableClearanceChecks_get()
static JSValue disableClearanceChecks_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.disableClearanceChecks;
return JS_NewBool(ctx, getGameState().cheats.disableClearanceChecks);
}
void disableClearanceChecks_set(bool value)
static JSValue disableClearanceChecks_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.disableClearanceChecks = value;
JS_UNPACK_BOOL(valueBool, ctx, value)
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.disableClearanceChecks = valueBool;
return JS_UNDEFINED;
}
bool disableLittering_get()
static JSValue disableLittering_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.disableLittering;
return JS_NewBool(ctx, getGameState().cheats.disableLittering);
}
void disableLittering_set(bool value)
static JSValue disableLittering_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.disableLittering = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.disableLittering = valueBool;
return JS_UNDEFINED;
}
bool disablePlantAging_get()
static JSValue disablePlantAging_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.disablePlantAging;
return JS_NewBool(ctx, getGameState().cheats.disablePlantAging);
}
void disablePlantAging_set(bool value)
static JSValue disablePlantAging_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.disablePlantAging = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.disablePlantAging = valueBool;
return JS_UNDEFINED;
}
bool allowRegularPathAsQueue_get()
static JSValue allowRegularPathAsQueue_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.allowRegularPathAsQueue;
return JS_NewBool(ctx, getGameState().cheats.allowRegularPathAsQueue);
}
void allowRegularPathAsQueue_set(bool value)
static JSValue allowRegularPathAsQueue_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.allowRegularPathAsQueue = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.allowRegularPathAsQueue = valueBool;
return JS_UNDEFINED;
}
bool allowSpecialColourSchemes_get()
static JSValue allowSpecialColourSchemes_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.allowSpecialColourSchemes;
return JS_NewBool(ctx, getGameState().cheats.allowSpecialColourSchemes);
}
void allowSpecialColourSchemes_set(bool value)
static JSValue allowSpecialColourSchemes_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.allowSpecialColourSchemes = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.allowSpecialColourSchemes = valueBool;
return JS_UNDEFINED;
}
bool disableRideValueAging_get()
static JSValue disableRideValueAging_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.disableRideValueAging;
return JS_NewBool(ctx, getGameState().cheats.disableRideValueAging);
}
void disableRideValueAging_set(bool value)
static JSValue disableRideValueAging_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.disableRideValueAging = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.disableRideValueAging = valueBool;
return JS_UNDEFINED;
}
bool disableSupportLimits_get()
static JSValue disableSupportLimits_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.disableSupportLimits;
return JS_NewBool(ctx, getGameState().cheats.disableSupportLimits);
}
void disableSupportLimits_set(bool value)
static JSValue disableSupportLimits_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.disableSupportLimits = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.disableSupportLimits = valueBool;
return JS_UNDEFINED;
}
bool disableTrainLengthLimit_get()
static JSValue disableTrainLengthLimit_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.disableTrainLengthLimit;
return JS_NewBool(ctx, getGameState().cheats.disableTrainLengthLimit);
}
void disableTrainLengthLimit_set(bool value)
static JSValue disableTrainLengthLimit_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.disableTrainLengthLimit = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.disableTrainLengthLimit = valueBool;
return JS_UNDEFINED;
}
bool disableVandalism_get()
static JSValue disableVandalism_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.disableVandalism;
return JS_NewBool(ctx, getGameState().cheats.disableVandalism);
}
void disableVandalism_set(bool value)
static JSValue disableVandalism_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.disableVandalism = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.disableVandalism = valueBool;
return JS_UNDEFINED;
}
bool enableAllDrawableTrackPieces_get()
static JSValue enableAllDrawableTrackPieces_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.enableAllDrawableTrackPieces;
return JS_NewBool(ctx, getGameState().cheats.enableAllDrawableTrackPieces);
}
void enableAllDrawableTrackPieces_set(bool value)
static JSValue enableAllDrawableTrackPieces_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.enableAllDrawableTrackPieces = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.enableAllDrawableTrackPieces = valueBool;
return JS_UNDEFINED;
}
bool enableChainLiftOnAllTrack_get()
static JSValue enableChainLiftOnAllTrack_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.enableChainLiftOnAllTrack;
return JS_NewBool(ctx, getGameState().cheats.enableChainLiftOnAllTrack);
}
void enableChainLiftOnAllTrack_set(bool value)
static JSValue enableChainLiftOnAllTrack_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.enableChainLiftOnAllTrack = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.enableChainLiftOnAllTrack = valueBool;
return JS_UNDEFINED;
}
bool fastLiftHill_get()
static JSValue fastLiftHill_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.unlockOperatingLimits;
return JS_NewBool(ctx, getGameState().cheats.unlockOperatingLimits);
}
void fastLiftHill_set(bool value)
static JSValue fastLiftHill_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.unlockOperatingLimits = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.unlockOperatingLimits = valueBool;
return JS_UNDEFINED;
}
bool freezeWeather_get()
static JSValue freezeWeather_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.freezeWeather;
return JS_NewBool(ctx, getGameState().cheats.freezeWeather);
}
void freezeWeather_set(bool value)
static JSValue freezeWeather_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.freezeWeather = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.freezeWeather = valueBool;
return JS_UNDEFINED;
}
bool ignoreResearchStatus_get()
static JSValue ignoreResearchStatus_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.ignoreResearchStatus;
return JS_NewBool(ctx, getGameState().cheats.ignoreResearchStatus);
}
void ignoreResearchStatus_set(bool value)
static JSValue ignoreResearchStatus_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.ignoreResearchStatus = value;
JS_UNPACK_BOOL(valueBool, ctx, value)
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.ignoreResearchStatus = valueBool;
return JS_UNDEFINED;
}
bool ignoreRideIntensity_get()
static JSValue ignoreRideIntensity_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.ignoreRideIntensity;
return JS_NewBool(ctx, getGameState().cheats.ignoreRideIntensity);
}
void ignoreRideIntensity_set(bool value)
static JSValue ignoreRideIntensity_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.ignoreRideIntensity = value;
JS_UNPACK_BOOL(valueBool, ctx, value)
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.ignoreRideIntensity = valueBool;
return JS_UNDEFINED;
}
bool ignoreRidePrice_get()
static JSValue ignoreRidePrice_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.ignorePrice;
return JS_NewBool(ctx, getGameState().cheats.ignorePrice);
}
void ignoreRidePrice_set(bool value)
static JSValue ignoreRidePrice_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.ignorePrice = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.ignorePrice = valueBool;
return JS_UNDEFINED;
}
bool neverendingMarketing_get()
static JSValue neverendingMarketing_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.neverendingMarketing;
return JS_NewBool(ctx, getGameState().cheats.neverendingMarketing);
}
void neverendingMarketing_set(bool value)
static JSValue neverendingMarketing_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.neverendingMarketing = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.neverendingMarketing = valueBool;
return JS_UNDEFINED;
}
int32_t forcedParkRating_get()
static JSValue forcedParkRating_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.forcedParkRating;
return JS_NewInt32(ctx, getGameState().cheats.forcedParkRating);
}
void forcedParkRating_set(int32_t value)
static JSValue forcedParkRating_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
int32_t adjusted = std::max(-1, std::min(value, 999));
JS_UNPACK_INT32(valueInt, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
int32_t adjusted = std::max(-1, std::min(valueInt, 999));
getGameState().cheats.forcedParkRating = adjusted;
Park::SetForcedRating(adjusted);
return JS_UNDEFINED;
}
bool sandboxMode_get()
static JSValue sandboxMode_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.sandboxMode;
return JS_NewBool(ctx, getGameState().cheats.sandboxMode);
}
void sandboxMode_set(bool value)
static JSValue sandboxMode_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.sandboxMode = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.sandboxMode = valueBool;
return JS_UNDEFINED;
}
bool showAllOperatingModes_get()
static JSValue showAllOperatingModes_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.showAllOperatingModes;
return JS_NewBool(ctx, getGameState().cheats.showAllOperatingModes);
}
void showAllOperatingModes_set(bool value)
static JSValue showAllOperatingModes_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.showAllOperatingModes = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.showAllOperatingModes = valueBool;
return JS_UNDEFINED;
}
bool showVehiclesFromOtherTrackTypes_get()
static JSValue showVehiclesFromOtherTrackTypes_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.showVehiclesFromOtherTrackTypes;
return JS_NewBool(ctx, getGameState().cheats.showVehiclesFromOtherTrackTypes);
}
void showVehiclesFromOtherTrackTypes_set(bool value)
static JSValue showVehiclesFromOtherTrackTypes_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.showVehiclesFromOtherTrackTypes = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.showVehiclesFromOtherTrackTypes = valueBool;
return JS_UNDEFINED;
}
bool makeAllDestructible_get()
static JSValue makeAllDestructible_get(JSContext* ctx, JSValue thisVal)
{
return getGameState().cheats.makeAllDestructible;
return JS_NewBool(ctx, getGameState().cheats.makeAllDestructible);
}
void makeAllDestructible_set(bool value)
static JSValue makeAllDestructible_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
getGameState().cheats.makeAllDestructible = value;
JS_UNPACK_BOOL(valueBool, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
getGameState().cheats.makeAllDestructible = valueBool;
return JS_UNDEFINED;
}
};
} // namespace OpenRCT2::Scripting
@@ -14,11 +14,13 @@
#include "../../../Context.h"
#include "../../../config/Config.h"
#include "../../../localisation/LocalisationService.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
namespace OpenRCT2::Scripting
{
class ScConfiguration;
extern ScConfiguration gScConfiguration;
enum class ScConfigurationKind
{
User,
@@ -26,36 +28,53 @@ namespace OpenRCT2::Scripting
Park
};
class ScConfiguration
class ScConfiguration final : public ScBase
{
private:
ScConfigurationKind _kind;
DukValue _backingObject;
public:
// context.configuration
ScConfiguration()
: _kind(ScConfigurationKind::User)
struct ConfigurationData
{
ScConfigurationKind _kind;
std::string _pluginName;
};
static JSValue GetParkStorageForPlugin(JSContext* ctx, const std::string& pluginName)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
JSValue parkStore = scriptEngine.GetParkStorage();
JSValue pluginStore = JS_GetPropertyStr(ctx, parkStore, pluginName.c_str());
// Create if it doesn't exist
if (!JS_IsObject(pluginStore))
{
JS_FreeValue(ctx, pluginStore);
pluginStore = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, parkStore, pluginName.c_str(), JS_DupValue(ctx, pluginStore));
}
return pluginStore;
}
// context.sharedStorage / context.getParkStorage
ScConfiguration(ScConfigurationKind kind, const DukValue& backingObject)
: _kind(kind)
, _backingObject(backingObject)
static JSValue GetStoreForConfigType(JSContext* ctx, const ConfigurationData* data)
{
switch (data->_kind)
{
case ScConfigurationKind::Park:
{
return GetParkStorageForPlugin(ctx, data->_pluginName);
}
case ScConfigurationKind::Shared:
{
auto& scriptEngine = GetContext()->GetScriptEngine();
return JS_DupValue(ctx, scriptEngine.GetSharedStorage());
}
case ScConfigurationKind::User:
default:
Guard::Fail("Invalid ScConfigurationKind");
return JS_UNDEFINED;
}
}
static void Register(duk_context* ctx)
{
dukglue_register_method(ctx, &ScConfiguration::getAll, "getAll");
dukglue_register_method(ctx, &ScConfiguration::get, "get");
dukglue_register_method(ctx, &ScConfiguration::set, "set");
dukglue_register_method(ctx, &ScConfiguration::has, "has");
}
private:
std::pair<std::string_view, std::string_view> GetNextNamespace(std::string_view input) const
static std::pair<std::string_view, std::string_view> GetNextNamespace(std::string_view input)
{
auto pos = input.find('.');
if (pos == std::string_view::npos)
@@ -66,16 +85,16 @@ namespace OpenRCT2::Scripting
return std::make_pair(input.substr(0, pos), input.substr(pos + 1));
}
std::pair<std::string_view, std::string_view> GetNamespaceAndKey(std::string_view input) const
static std::pair<std::string_view, std::string_view> GetNamespaceAndKey(std::string_view input)
{
auto pos = input.find_last_of('.');
return pos == std::string_view::npos ? std::make_pair(std::string_view(), input)
: std::make_pair(input.substr(0, pos), input.substr(pos + 1));
}
std::optional<DukValue> GetNamespaceObject(std::string_view ns) const
static JSValue GetNamespaceObject(JSContext* ctx, const ConfigurationData* data, std::string_view ns)
{
auto store = _backingObject;
auto store = GetStoreForConfigType(ctx, data);
if (!ns.empty())
{
auto k = ns;
@@ -83,17 +102,19 @@ namespace OpenRCT2::Scripting
do
{
auto [next, remainder] = GetNextNamespace(k);
store = store[next];
auto oldStore = store;
store = JS_GetPropertyStr(ctx, store, std::string(next).c_str());
JS_FreeValue(ctx, oldStore);
k = remainder;
end = store.type() == DukValue::Type::UNDEFINED || remainder.empty();
end = JS_IsUndefined(store) || remainder.empty();
} while (!end);
}
return store.type() == DukValue::OBJECT ? std::make_optional(store) : std::nullopt;
return store;
}
DukValue GetOrCreateNamespaceObject(duk_context* ctx, std::string_view ns) const
static JSValue GetOrCreateNamespaceObject(JSContext* ctx, const ConfigurationData* data, std::string_view ns)
{
auto store = _backingObject;
auto store = GetStoreForConfigType(ctx, data);
if (!ns.empty())
{
std::string_view k = ns;
@@ -101,19 +122,21 @@ namespace OpenRCT2::Scripting
do
{
auto [next, remainder] = GetNextNamespace(k);
auto subStore = store[next];
std::string nextStr(next);
k = remainder;
if (subStore.type() == DukValue::Type::UNDEFINED)
auto subStore = JS_GetPropertyStr(ctx, store, nextStr.c_str());
auto oldStore = store;
if (JS_IsUndefined(subStore))
{
store.push();
duk_push_object(ctx);
store = DukValue::copy_from_stack(ctx);
duk_put_prop_lstring(ctx, -2, next.data(), next.size());
duk_pop(ctx);
store = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, oldStore, nextStr.c_str(), JS_DupValue(ctx, store));
JS_FreeValue(ctx, oldStore);
}
else
{
store = std::move(subStore);
store = subStore;
JS_FreeValue(ctx, oldStore);
}
end = remainder.empty();
} while (!end);
@@ -121,14 +144,14 @@ namespace OpenRCT2::Scripting
return store;
}
bool IsValidNamespace(std::string_view ns) const
static bool IsValidNamespace(std::string_view ns, ScConfigurationKind kind)
{
if (!ns.empty() && (ns[0] == '.' || ns[ns.size() - 1] == '.'))
{
return false;
}
if (_kind != ScConfigurationKind::Park)
if (kind != ScConfigurationKind::Park)
{
if (ns.empty())
{
@@ -146,154 +169,211 @@ namespace OpenRCT2::Scripting
return true;
}
bool IsValidKey(std::string_view key) const
static bool IsValidKey(std::string_view key)
{
return !key.empty() && key.find('.') == std::string_view::npos;
}
DukValue getAll(const DukValue& dukNamespace) const
static JSValue getAll(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
DukValue result;
auto ctx = GetContext()->GetScriptEngine().GetContext();
JSValue jsNamespace = argv[0];
ConfigurationData* data = gScConfiguration.GetOpaque<ConfigurationData*>(thisVal);
std::string ns = "";
if (dukNamespace.type() == DukValue::Type::STRING)
if (JS_IsString(jsNamespace))
{
ns = dukNamespace.as_string();
ns = JSToStdString(ctx, jsNamespace);
}
else if (dukNamespace.type() != DukValue::Type::UNDEFINED)
else if (!JS_IsUndefined(jsNamespace))
{
duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid.");
JS_ThrowPlainError(ctx, "Namespace was invalid.");
return JS_EXCEPTION;
}
if (IsValidNamespace(ns))
if (IsValidNamespace(ns, data->_kind))
{
if (_kind == ScConfigurationKind::User)
if (data->_kind == ScConfigurationKind::User)
{
DukObject obj(ctx);
JSValue obj = JS_NewObject(ctx);
if (ns == "general")
{
obj.Set("general.language", Config::Get().general.language);
obj.Set("general.showFps", Config::Get().general.showFPS);
auto& localisationService = GetContext()->GetLocalisationService();
auto locale = localisationService.GetCurrentLanguageLocale();
JS_SetPropertyStr(ctx, obj, "general.language", JSFromStdString(ctx, locale));
JS_SetPropertyStr(ctx, obj, "general.showFps", JS_NewBool(ctx, Config::Get().general.showFPS));
}
result = obj.Take();
return obj;
}
else
{
auto obj = GetNamespaceObject(ns);
result = obj ? *obj : DukObject(ctx).Take();
auto obj = GetNamespaceObject(ctx, data, ns);
if (!JS_IsObject(obj))
{
JS_FreeValue(ctx, obj);
return JS_NewObject(ctx);
}
return obj;
}
}
else
{
duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid.");
JS_ThrowPlainError(ctx, "Namespace was invalid.");
return JS_EXCEPTION;
}
return result;
}
DukValue get(const std::string& key, const DukValue& defaultValue) const
static JSValue get(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
if (_kind == ScConfigurationKind::User)
JS_UNPACK_STR(key, ctx, argv[0]);
JSValue defaultValue = argv[1];
ConfigurationData* data = gScConfiguration.GetOpaque<ConfigurationData*>(thisVal);
if (data->_kind == ScConfigurationKind::User)
{
if (key == "general.language")
{
auto& localisationService = GetContext()->GetLocalisationService();
auto locale = localisationService.GetCurrentLanguageLocale();
duk_push_lstring(ctx, locale.data(), locale.size());
return DukValue::take_from_stack(ctx);
return JSFromStdString(ctx, locale);
}
if (key == "general.showFps")
{
duk_push_boolean(ctx, Config::Get().general.showFPS);
return DukValue::take_from_stack(ctx);
return JS_NewBool(ctx, Config::Get().general.showFPS);
}
}
else
{
auto [ns, n] = GetNamespaceAndKey(key);
if (!IsValidNamespace(ns))
if (!IsValidNamespace(ns, data->_kind))
{
duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid.");
JS_ThrowPlainError(ctx, "Namespace was invalid.");
return JS_EXCEPTION;
}
else if (!IsValidKey(n))
{
duk_error(ctx, DUK_ERR_ERROR, "Key was invalid.");
JS_ThrowPlainError(ctx, "Key was invalid.");
return JS_EXCEPTION;
}
else
{
auto obj = GetNamespaceObject(ns);
if (obj)
auto obj = GetNamespaceObject(ctx, data, ns);
if (JS_IsObject(obj))
{
auto val = (*obj)[n];
if (val.type() != DukValue::Type::UNDEFINED)
auto val = JS_GetPropertyStr(ctx, obj, std::string(n).c_str());
JS_FreeValue(ctx, obj);
if (!JS_IsUndefined(val))
{
return val;
}
}
else
{
JS_FreeValue(ctx, obj);
}
}
}
return defaultValue;
return JS_DupValue(ctx, defaultValue);
}
void set(const std::string& key, const DukValue& value) const
static JSValue set(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
if (_kind == ScConfigurationKind::User)
JS_UNPACK_STR(key, ctx, argv[0]);
JSValue value = argv[1];
ConfigurationData* data = gScConfiguration.GetOpaque<ConfigurationData*>(thisVal);
if (data->_kind == ScConfigurationKind::User)
{
try
if (key == "general.showFps")
{
if (key == "general.showFps")
if (JS_IsBool(value))
{
Config::Get().general.showFPS = value.as_bool();
Config::Get().general.showFPS = JS_ToBool(ctx, value) > 0;
return JS_UNDEFINED;
}
else
{
duk_error(ctx, DUK_ERR_ERROR, "Property does not exist.");
JS_ThrowPlainError(ctx, "Invalid value for this property.");
return JS_EXCEPTION;
}
}
catch (const DukException&)
else
{
duk_error(ctx, DUK_ERR_ERROR, "Invalid value for this property.");
JS_ThrowPlainError(ctx, "Property does not exist.");
return JS_EXCEPTION;
}
}
else
{
auto [ns, n] = GetNamespaceAndKey(key);
if (!IsValidNamespace(ns))
if (!IsValidNamespace(ns, data->_kind))
{
duk_error(ctx, DUK_ERR_ERROR, "Namespace was invalid.");
JS_ThrowPlainError(ctx, "Namespace was invalid.");
return JS_EXCEPTION;
}
else if (!IsValidKey(n))
{
duk_error(ctx, DUK_ERR_ERROR, "Key was invalid.");
JS_ThrowPlainError(ctx, "Key was invalid.");
return JS_EXCEPTION;
}
else
{
auto obj = GetOrCreateNamespaceObject(ctx, ns);
obj.push();
if (value.type() == DukValue::Type::UNDEFINED)
{
duk_del_prop_lstring(ctx, -1, n.data(), n.size());
}
else
{
value.push();
duk_put_prop_lstring(ctx, -2, n.data(), n.size());
}
duk_pop(ctx);
auto obj = GetOrCreateNamespaceObject(ctx, data, ns);
JS_SetPropertyStr(ctx, obj, std::string(n).c_str(), JS_DupValue(ctx, value));
JS_FreeValue(ctx, obj);
auto& scriptEngine = GetContext()->GetScriptEngine();
scriptEngine.SaveSharedStorage();
return JS_UNDEFINED;
}
}
}
bool has(const std::string& key) const
static JSValue has(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto val = get(key, DukValue());
return val.type() != DukValue::Type::UNDEFINED;
std::array newArgs = { argv[0], JS_UNDEFINED };
auto val = get(ctx, thisVal, 2, newArgs.data());
if (JS_IsException(val))
{
return val;
}
bool retval = !JS_IsUndefined(val);
JS_FreeValue(ctx, val);
return JS_NewBool(ctx, retval);
}
static constexpr JSCFunctionListEntry funcs[] = {
JS_CFUNC_DEF("getAll", 1, ScConfiguration::getAll),
JS_CFUNC_DEF("get", 2, ScConfiguration::get),
JS_CFUNC_DEF("set", 2, ScConfiguration::set),
JS_CFUNC_DEF("has", 1, ScConfiguration::has),
};
public:
// context.configuration
JSValue New(JSContext* ctx)
{
return MakeWithOpaque(ctx, funcs, new ConfigurationData{ ScConfigurationKind::User, {} });
}
// context.sharedStorage / context.getParkStorage
JSValue New(JSContext* ctx, ScConfigurationKind kind, std::string_view pluginName = {})
{
return MakeWithOpaque(ctx, funcs, new ConfigurationData{ kind, std::string(pluginName) });
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Configuration", Finalize);
}
static void Finalize(JSRuntime* rt, JSValue thisVal)
{
ConfigurationData* data = gScConfiguration.GetOpaque<ConfigurationData*>(thisVal);
if (data)
delete data;
}
private:
};
} // namespace OpenRCT2::Scripting
@@ -12,55 +12,64 @@
#ifdef ENABLE_SCRIPTING
#include "../../../interface/InteractiveConsole.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
namespace OpenRCT2::Scripting
{
class ScConsole
class ScConsole;
extern ScConsole gScConsole;
class ScConsole final : public ScBase
{
private:
InteractiveConsole& _console;
static JSValue clear(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
if (const auto console = gScConsole.GetOpaque<InteractiveConsole*>(thisVal))
console->Clear();
return JS_UNDEFINED;
}
static JSValue log(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
if (const auto console = gScConsole.GetOpaque<InteractiveConsole*>(thisVal))
{
std::string line;
for (int i = 0; i < argc; i++)
{
if (i != 0)
line.push_back(' ');
line += Stringify(ctx, argv[i]);
}
console->WriteLine(line);
}
return JS_UNDEFINED;
}
static JSValue executeLegacy(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
if (const auto console = gScConsole.GetOpaque<InteractiveConsole*>(thisVal))
{
JS_UNPACK_STR(str, ctx, argv[0]);
console->Execute(str);
}
return JS_UNDEFINED;
}
static constexpr JSCFunctionListEntry funcs[] = {
JS_CFUNC_DEF("clear", 0, clear),
JS_CFUNC_DEF("log", 0, log),
JS_CFUNC_DEF("executeLegacy", 1, executeLegacy),
};
public:
ScConsole(InteractiveConsole& console)
: _console(console)
JSValue New(JSContext* ctx, InteractiveConsole& console)
{
return MakeWithOpaque(ctx, funcs, &console);
}
void clear()
void Register(JSContext* ctx)
{
_console.Clear();
}
duk_ret_t log(duk_context* ctx)
{
std::string line;
auto nargs = duk_get_top(ctx);
for (duk_idx_t i = 0; i < nargs; i++)
{
auto arg = DukValue::copy_from_stack(ctx, i);
auto argsz = Stringify(arg);
if (i != 0)
{
line.push_back(' ');
}
line += argsz;
}
_console.WriteLine(line);
return 0;
}
void executeLegacy(const std::string& command)
{
_console.Execute(command);
}
static void Register(duk_context* ctx)
{
dukglue_register_method(ctx, &ScConsole::clear, "clear");
dukglue_register_method_varargs(ctx, &ScConsole::log, "log");
dukglue_register_method(ctx, &ScConsole::executeLegacy, "executeLegacy");
RegisterBaseStr(ctx, "Console");
}
};
} // namespace OpenRCT2::Scripting
+251 -283
View File
@@ -18,7 +18,6 @@
#include "../../../localisation/Formatting.h"
#include "../../../object/ObjectManager.h"
#include "../../../scenario/Scenario.h"
#include "../../Duktape.hpp"
#include "../../HookEngine.h"
#include "../../IconNames.hpp"
#include "../../ScriptEngine.h"
@@ -32,459 +31,428 @@
namespace OpenRCT2::Scripting
{
class ScContext
class ScContext;
extern ScContext gScContext;
class ScContext final : public ScBase
{
private:
ScriptExecutionInfo& _execInfo;
HookEngine& _hookEngine;
public:
ScContext(ScriptExecutionInfo& execInfo, HookEngine& hookEngine)
: _execInfo(execInfo)
, _hookEngine(hookEngine)
static JSValue apiVersion_get(JSContext* ctx, JSValue thisVal)
{
return JS_NewInt32(ctx, kPluginApiVersion);
}
private:
int32_t apiVersion_get()
static JSValue configuration_get(JSContext* ctx, JSValue thisVal)
{
return kPluginApiVersion;
return gScConfiguration.New(ctx);
}
std::shared_ptr<ScConfiguration> configuration_get()
static JSValue sharedStorage_get(JSContext* ctx, JSValue thisVal)
{
return std::make_shared<ScConfiguration>();
return gScConfiguration.New(ctx, ScConfigurationKind::Shared);
}
std::shared_ptr<ScConfiguration> sharedStorage_get()
static JSValue getParkStorage(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JSValue jsPluginName = argv[0];
auto& scriptEngine = GetContext()->GetScriptEngine();
return std::make_shared<ScConfiguration>(ScConfigurationKind::Shared, scriptEngine.GetSharedStorage());
}
std::shared_ptr<ScConfiguration> GetParkStorageForPlugin(std::string_view pluginName)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto parkStore = scriptEngine.GetParkStorage();
auto pluginStore = parkStore[pluginName];
// Create if it doesn't exist
if (pluginStore.type() != DukValue::Type::OBJECT)
JSValue result = JS_UNDEFINED;
if (JS_IsString(jsPluginName))
{
auto* ctx = scriptEngine.GetContext();
parkStore.push();
duk_push_object(ctx);
duk_put_prop_lstring(ctx, -2, pluginName.data(), pluginName.size());
duk_pop(ctx);
pluginStore = parkStore[pluginName];
}
return std::make_shared<ScConfiguration>(ScConfigurationKind::Park, pluginStore);
}
std::shared_ptr<ScConfiguration> getParkStorage(const DukValue& dukPluginName)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
std::shared_ptr<ScConfiguration> result;
if (dukPluginName.type() == DukValue::Type::STRING)
{
auto& pluginName = dukPluginName.as_string();
std::string pluginName = JSToStdString(ctx, jsPluginName);
if (pluginName.empty())
{
duk_error(scriptEngine.GetContext(), DUK_ERR_ERROR, "Plugin name is empty");
return JS_ThrowPlainError(ctx, "Plugin name is empty");
}
result = GetParkStorageForPlugin(pluginName);
result = gScConfiguration.New(ctx, ScConfigurationKind::Park, pluginName);
}
else if (dukPluginName.type() == DukValue::Type::UNDEFINED)
else if (JS_IsUndefined(jsPluginName))
{
auto plugin = _execInfo.GetCurrentPlugin();
const auto& plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
if (plugin == nullptr)
{
duk_error(
scriptEngine.GetContext(), DUK_ERR_ERROR, "Plugin name must be specified when used from console.");
return JS_ThrowPlainError(ctx, "Plugin name must be specified when used from console.");
}
result = GetParkStorageForPlugin(plugin->GetMetadata().Name);
result = gScConfiguration.New(ctx, ScConfigurationKind::Park, plugin->GetMetadata().Name);
}
else
{
duk_error(scriptEngine.GetContext(), DUK_ERR_ERROR, "Invalid plugin name.");
return JS_ThrowPlainError(ctx, "Invalid plugin name.");
}
return result;
}
std::string mode_get()
static JSValue mode_get(JSContext* ctx, JSValue thisVal)
{
if (gLegacyScene == LegacyScene::titleSequence)
return "title";
return JSFromStdString(ctx, "title");
else if (gLegacyScene == LegacyScene::scenarioEditor)
return "scenario_editor";
return JSFromStdString(ctx, "scenario_editor");
else if (gLegacyScene == LegacyScene::trackDesigner)
return "track_designer";
return JSFromStdString(ctx, "track_designer");
else if (gLegacyScene == LegacyScene::trackDesignsManager)
return "track_manager";
return "normal";
return JSFromStdString(ctx, "track_manager");
return JSFromStdString(ctx, "normal");
}
bool paused_get()
static JSValue paused_get(JSContext* ctx, JSValue thisVal)
{
return GameIsPaused();
return JS_NewBool(ctx, GameIsPaused());
}
void paused_set(const bool& value)
static JSValue paused_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
ThrowIfGameStateNotMutable();
if (value != GameIsPaused())
JS_UNPACK_BOOL(valueBool, ctx, value)
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
if (valueBool != GameIsPaused())
PauseToggle();
return JS_UNDEFINED;
}
void captureImage(const DukValue& options)
static JSValue captureImage(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
JSValue options = argv[0];
try
{
CaptureOptions captureOptions;
captureOptions.Filename = fs::u8path(AsOrDefault(options["filename"], ""));
captureOptions.Rotation = options["rotation"].as_uint() & 3;
captureOptions.Zoom = ZoomLevel(options["zoom"].as_uint());
captureOptions.Transparent = AsOrDefault(options["transparent"], false);
auto dukPosition = options["position"];
if (dukPosition.type() == DukValue::Type::OBJECT)
auto rotation = JSToOptionalInt(ctx, options, "rotation");
auto zoom = JSToOptionalInt(ctx, options, "zoom");
if (!rotation.has_value() || !zoom.has_value())
{
JS_ThrowPlainError(ctx, "Invalid options.");
return JS_EXCEPTION;
}
CaptureOptions captureOptions;
captureOptions.Filename = fs::u8path(AsOrDefault(ctx, options, "filename", ""));
captureOptions.Rotation = rotation.value() & 3;
captureOptions.Zoom = ZoomLevel(zoom.value());
captureOptions.Transparent = AsOrDefault(ctx, options, "transparent", false);
JSValue jsPosition = JS_GetPropertyStr(ctx, options, "position");
if (JS_IsObject(jsPosition))
{
auto width = JSToOptionalInt(ctx, options, "width");
auto height = JSToOptionalInt(ctx, options, "height");
auto x = JSToOptionalInt(ctx, jsPosition, "x");
auto y = JSToOptionalInt(ctx, jsPosition, "y");
if (!width.has_value() || !height.has_value() || !x.has_value() || !y.has_value())
{
JS_FreeValue(ctx, jsPosition);
JS_ThrowPlainError(ctx, "Invalid options.");
return JS_EXCEPTION;
}
CaptureView view;
view.Width = options["width"].as_int();
view.Height = options["height"].as_int();
view.Position.x = dukPosition["x"].as_int();
view.Position.y = dukPosition["y"].as_int();
view.Width = width.value();
view.Height = height.value();
view.Position.x = x.value();
view.Position.y = y.value();
captureOptions.View = view;
}
JS_FreeValue(ctx, jsPosition);
CaptureImage(captureOptions);
}
catch (const DukException&)
{
duk_error(ctx, DUK_ERR_ERROR, "Invalid options.");
}
catch (const std::exception& ex)
{
duk_error(ctx, DUK_ERR_ERROR, ex.what());
JS_ThrowPlainError(ctx, "%s", ex.what());
return JS_EXCEPTION;
}
return JS_UNDEFINED;
}
DukValue getObject(const std::string& typez, int32_t index) const
static JSValue getObject(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
// deprecated function, moved to ObjectManager.getObject.
ScObjectManager objectManager;
return objectManager.getObject(typez, index);
return gScObjectManager.getObject(ctx, thisVal, argc, argv);
}
std::vector<DukValue> getAllObjects(const std::string& typez) const
static JSValue getAllObjects(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
// deprecated function, moved to ObjectManager.getAllObjects.
ScObjectManager objectManager;
return objectManager.getAllObjects(typez);
return gScObjectManager.getAllObjects(ctx, thisVal, argc, argv);
}
DukValue getTrackSegment(uint16_t type)
static JSValue getTrackSegment(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
JS_UNPACK_INT32(type, ctx, argv[0])
if (type >= EnumValue(TrackElemType::count))
{
return ToDuk(ctx, nullptr);
return JS_NULL;
}
else
{
return GetObjectAsDukValue(ctx, std::make_shared<ScTrackSegment>(static_cast<TrackElemType>(type)));
return gScTrackSegment.New(ctx, static_cast<TrackElemType>(type));
}
}
std::vector<DukValue> getAllTrackSegments()
static JSValue getAllTrackSegments(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
std::vector<DukValue> result;
auto result = JS_NewArray(ctx);
int64_t index = 0;
for (uint16_t type = 0; type < EnumValue(TrackElemType::count); type++)
{
auto obj = std::make_shared<ScTrackSegment>(static_cast<TrackElemType>(type));
if (obj != nullptr)
{
result.push_back(GetObjectAsDukValue(ctx, obj));
}
auto obj = gScTrackSegment.New(ctx, static_cast<TrackElemType>(type));
JS_SetPropertyInt64(ctx, result, index++, obj);
}
return result;
}
int32_t getRandom(int32_t min, int32_t max)
static JSValue getRandom(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
ThrowIfGameStateNotMutable();
JS_UNPACK_INT32(min, ctx, argv[0]);
JS_UNPACK_INT32(max, ctx, argv[1]);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
if (min >= max)
return min;
return JS_NewInt32(ctx, min);
int32_t range = max - min;
return min + ScenarioRandMax(range);
return JS_NewInt64(ctx, min + ScenarioRandMax(range));
}
duk_ret_t formatString(duk_context* ctx)
static JSValue formatString(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto nargs = duk_get_top(ctx);
if (nargs >= 1)
if (argc >= 1)
{
auto dukFmt = DukValue::copy_from_stack(ctx, 0);
if (dukFmt.type() == DukValue::Type::STRING)
const JSValue jsFmt = argv[0];
if (JS_IsString(jsFmt))
{
FmtString fmt(dukFmt.as_string());
FmtString fmt(JSToStdString(ctx, jsFmt));
std::vector<FormatArg_t> args;
for (duk_idx_t i = 1; i < nargs; i++)
for (int i = 1; i < argc; i++)
{
auto dukArg = DukValue::copy_from_stack(ctx, i);
switch (dukArg.type())
const JSValue jsArg = argv[i];
if (JS_IsNumber(jsArg))
{
case DukValue::Type::NUMBER:
args.push_back(dukArg.as_int());
break;
case DukValue::Type::STRING:
args.push_back(dukArg.as_string());
break;
default:
duk_error(ctx, DUK_ERR_ERROR, "Invalid format argument.");
break;
args.emplace_back(JSToInt(ctx, jsArg));
}
else if (JS_IsString(jsArg))
{
args.emplace_back(JSToStdString(ctx, jsArg));
}
else
{
JS_ThrowPlainError(ctx, "Invalid format argument.");
return JS_EXCEPTION;
}
}
auto result = FormatStringAny(fmt, args);
duk_push_lstring(ctx, result.c_str(), result.size());
return JSFromStdString(ctx, result);
}
else
{
duk_error(ctx, DUK_ERR_ERROR, "Invalid format string.");
JS_ThrowPlainError(ctx, "Invalid format string.");
return JS_EXCEPTION;
}
}
else
{
duk_error(ctx, DUK_ERR_ERROR, "Invalid format string.");
}
return 1;
JS_ThrowPlainError(ctx, "Invalid format string.");
return JS_EXCEPTION;
}
#ifdef _MSC_VER
// HACK workaround to resolve issue #14853
// The exception thrown in duk_error was causing a crash when RAII kicked in for this lambda.
// Only ensuring it was not in the same generated method fixed it.
__declspec(noinline)
#endif
std::shared_ptr<ScDisposable>
CreateSubscription(HookType hookType, const DukValue& callback)
static JSValue subscribe(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto owner = _execInfo.GetCurrentPlugin();
auto cookie = _hookEngine.Subscribe(hookType, owner, callback);
return std::make_shared<ScDisposable>([this, hookType, cookie]() { _hookEngine.Unsubscribe(hookType, cookie); });
}
JS_UNPACK_STR(hook, ctx, argv[0]);
JS_UNPACK_CALLBACK(callback, ctx, argv[1]);
std::shared_ptr<ScDisposable> subscribe(const std::string& hook, const DukValue& callback)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
auto hookType = GetHookType(hook);
if (hookType == HookType::notDefined)
{
duk_error(ctx, DUK_ERR_ERROR, "Unknown hook type");
JS_ThrowPlainError(ctx, "Unknown hook type");
return JS_EXCEPTION;
}
if (!callback.is_function())
{
duk_error(ctx, DUK_ERR_ERROR, "Expected function for callback");
}
auto owner = _execInfo.GetCurrentPlugin();
auto owner = scriptEngine.GetExecInfo().GetCurrentPlugin();
if (owner == nullptr)
{
duk_error(ctx, DUK_ERR_ERROR, "Not in a plugin context");
JS_ThrowPlainError(ctx, "Not in a plugin context");
return JS_EXCEPTION;
}
if (!_hookEngine.IsValidHookForPlugin(hookType, *owner))
auto& hookEngine = scriptEngine.GetHookEngine();
if (!hookEngine.IsValidHookForPlugin(hookType, *owner))
{
duk_error(ctx, DUK_ERR_ERROR, "Hook type not available for this plugin type.");
JS_ThrowPlainError(ctx, "Hook type not available for this plugin type.");
return JS_EXCEPTION;
}
return CreateSubscription(hookType, callback);
auto cookie = hookEngine.Subscribe(hookType, owner, callback);
return gScDisposable.New(ctx, [&hookEngine, hookType, cookie]() { hookEngine.Unsubscribe(hookType, cookie); });
}
void queryAction(const std::string& action, const DukValue& args, const DukValue& callback)
static JSValue queryAction(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
QueryOrExecuteAction(action, args, callback, false);
JS_UNPACK_STR(action, ctx, argv[0]);
JS_UNPACK_OBJECT(args, ctx, argv[1]);
return QueryOrExecuteAction(ctx, action, args, JSCallback(ctx, argv[2]), false);
}
void executeAction(const std::string& action, const DukValue& args, const DukValue& callback)
static JSValue executeAction(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
QueryOrExecuteAction(action, args, callback, true);
JS_UNPACK_STR(action, ctx, argv[0]);
JS_UNPACK_OBJECT(args, ctx, argv[1]);
return QueryOrExecuteAction(ctx, action, args, JSCallback(ctx, argv[2]), true);
}
void QueryOrExecuteAction(const std::string& actionid, const DukValue& args, const DukValue& callback, bool isExecute)
static JSValue QueryOrExecuteAction(
JSContext* ctx, const std::string& actionid, JSValue args, const JSCallback& callback, bool isExecute)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
try
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
auto pair = scriptEngine.CreateGameAction(ctx, actionid, args, plugin->GetMetadata().Name);
std::unique_ptr<GameActions::GameAction> action = std::move(pair.first);
if (pair.second)
return JS_ThrowPlainError(ctx, "Invalid action parameters.");
if (action != nullptr)
{
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
auto action = scriptEngine.CreateGameAction(actionid, args, plugin->GetMetadata().Name);
if (action != nullptr)
if (isExecute)
{
if (isExecute)
{
action->SetCallback(
[this, plugin,
callback](const GameActions::GameAction* act, const GameActions::Result* res) -> void {
HandleGameActionResult(plugin, *act, *res, callback);
});
GameActions::Execute(action.get(), getGameState());
}
else
{
auto res = GameActions::Query(action.get(), getGameState());
HandleGameActionResult(plugin, *action, res, callback);
}
action->SetCallback(
[plugin, callback](const GameActions::GameAction* act, const GameActions::Result* res) -> void {
HandleGameActionResult(plugin, *act, *res, callback);
});
GameActions::Execute(action.get(), getGameState());
}
else
{
duk_error(ctx, DUK_ERR_ERROR, "Unknown action.");
auto res = GameActions::Query(action.get(), getGameState());
HandleGameActionResult(plugin, *action, res, callback);
}
}
catch (DukException&)
{
duk_error(ctx, DUK_ERR_ERROR, "Invalid action parameters.");
}
}
void HandleGameActionResult(
const std::shared_ptr<Plugin>& plugin, const GameActions::GameAction& action, const GameActions::Result& res,
const DukValue& callback)
{
if (callback.is_function())
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto dukResult = scriptEngine.GameActionResultToDuk(action, res);
// Call the plugin callback and pass the result object
scriptEngine.ExecutePluginCall(plugin, callback, { dukResult }, false);
}
}
void registerAction(const std::string& action, const DukValue& query, const DukValue& execute)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
auto ctx = scriptEngine.GetContext();
if (!query.is_function())
{
duk_error(ctx, DUK_ERR_ERROR, "query was not a function.");
}
else if (!execute.is_function())
{
duk_error(ctx, DUK_ERR_ERROR, "execute was not a function.");
}
else if (!scriptEngine.RegisterCustomAction(plugin, action, query, execute))
{
duk_error(ctx, DUK_ERR_ERROR, "action has already been registered.");
}
}
int32_t SetIntervalOrTimeout(DukValue callback, int32_t delay, bool repeat)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
int32_t handle = 0;
if (callback.is_function())
{
handle = scriptEngine.AddInterval(plugin, delay, repeat, std::move(callback));
}
else
{
duk_error(ctx, DUK_ERR_ERROR, "callback was not a function.");
return JS_ThrowPlainError(ctx, "Unknown action.");
}
return handle;
return JS_UNDEFINED;
}
void ClearIntervalOrTimeout(int32_t handle)
static void HandleGameActionResult(
const std::shared_ptr<Plugin>& plugin, const GameActions::GameAction& action, const GameActions::Result& res,
const JSCallback& callback)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
JSContext* ctx = plugin ? plugin->GetContext() : scriptEngine.GetContext();
JSValue jsResult = scriptEngine.GameActionResultToJS(ctx, action, res);
// Call the plugin callback and pass the result object
scriptEngine.ExecutePluginCall(plugin, callback.callback, { jsResult }, false);
}
static JSValue registerAction(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_STR(action, ctx, argv[0]);
JS_UNPACK_CALLBACK(query, ctx, argv[1]);
JS_UNPACK_CALLBACK(execute, ctx, argv[2]);
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
if (!scriptEngine.RegisterCustomAction(plugin, action, query, execute))
{
JS_ThrowPlainError(ctx, "action has already been registered.");
return JS_EXCEPTION;
}
return JS_UNDEFINED;
}
static int32_t SetIntervalOrTimeout(const JSCallback& callback, int32_t delay, bool repeat)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
return scriptEngine.AddInterval(plugin, delay, repeat, callback);
}
static void ClearIntervalOrTimeout(int32_t handle)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
scriptEngine.RemoveInterval(plugin, handle);
}
int32_t setInterval(DukValue callback, int32_t delay)
static JSValue setInterval(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
return SetIntervalOrTimeout(callback, delay, true);
JS_UNPACK_CALLBACK(callback, ctx, argv[0]);
JS_UNPACK_INT32(delay, ctx, argv[1]);
return JS_NewInt32(ctx, SetIntervalOrTimeout(callback, delay, true));
}
int32_t setTimeout(DukValue callback, int32_t delay)
static JSValue setTimeout(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
return SetIntervalOrTimeout(callback, delay, false);
JS_UNPACK_CALLBACK(callback, ctx, argv[0]);
JS_UNPACK_INT32(delay, ctx, argv[1]);
return JS_NewInt32(ctx, SetIntervalOrTimeout(callback, delay, false));
}
void clearInterval(int32_t handle)
static JSValue clearInterval(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_INT32(handle, ctx, argv[0]);
ClearIntervalOrTimeout(handle);
return JS_UNDEFINED;
}
void clearTimeout(int32_t handle)
static JSValue clearTimeout(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_INT32(handle, ctx, argv[0]);
ClearIntervalOrTimeout(handle);
return JS_UNDEFINED;
}
int32_t getIcon(const std::string& iconName)
static JSValue getIcon(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
return GetIconByName(iconName);
JS_UNPACK_STR(iconName, ctx, argv[0]);
return JS_NewInt64(ctx, GetIconByName(iconName));
}
public:
static void Register(duk_context* ctx)
void Register(JSContext* ctx)
{
dukglue_register_property(ctx, &ScContext::apiVersion_get, nullptr, "apiVersion");
dukglue_register_property(ctx, &ScContext::configuration_get, nullptr, "configuration");
dukglue_register_property(ctx, &ScContext::sharedStorage_get, nullptr, "sharedStorage");
dukglue_register_method(ctx, &ScContext::getParkStorage, "getParkStorage");
dukglue_register_property(ctx, &ScContext::mode_get, nullptr, "mode");
dukglue_register_property(ctx, &ScContext::paused_get, &ScContext::paused_set, "paused");
dukglue_register_method(ctx, &ScContext::captureImage, "captureImage");
dukglue_register_method(ctx, &ScContext::getObject, "getObject");
dukglue_register_method(ctx, &ScContext::getAllObjects, "getAllObjects");
dukglue_register_method(ctx, &ScContext::getTrackSegment, "getTrackSegment");
dukglue_register_method(ctx, &ScContext::getAllTrackSegments, "getAllTrackSegments");
dukglue_register_method(ctx, &ScContext::getRandom, "getRandom");
dukglue_register_method_varargs(ctx, &ScContext::formatString, "formatString");
dukglue_register_method(ctx, &ScContext::subscribe, "subscribe");
dukglue_register_method(ctx, &ScContext::queryAction, "queryAction");
dukglue_register_method(ctx, &ScContext::executeAction, "executeAction");
dukglue_register_method(ctx, &ScContext::registerAction, "registerAction");
dukglue_register_method(ctx, &ScContext::setInterval, "setInterval");
dukglue_register_method(ctx, &ScContext::setTimeout, "setTimeout");
dukglue_register_method(ctx, &ScContext::clearInterval, "clearInterval");
dukglue_register_method(ctx, &ScContext::clearTimeout, "clearTimeout");
dukglue_register_method(ctx, &ScContext::getIcon, "getIcon");
RegisterBaseStr(ctx, "Context");
}
JSValue New(JSContext* ctx)
{
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("apiVersion", ScContext::apiVersion_get, nullptr),
JS_CGETSET_DEF("configuration", ScContext::configuration_get, nullptr),
JS_CGETSET_DEF("sharedStorage", ScContext::sharedStorage_get, nullptr),
JS_CFUNC_DEF("getParkStorage", 1, ScContext::getParkStorage),
JS_CGETSET_DEF("mode", ScContext::mode_get, nullptr),
JS_CGETSET_DEF("paused", ScContext::paused_get, &ScContext::paused_set),
JS_CFUNC_DEF("captureImage", 1, ScContext::captureImage),
JS_CFUNC_DEF("getObject", 2, ScContext::getObject),
JS_CFUNC_DEF("getAllObjects", 2, ScContext::getAllObjects),
JS_CFUNC_DEF("getTrackSegment", 1, ScContext::getTrackSegment),
JS_CFUNC_DEF("getAllTrackSegments", 0, ScContext::getAllTrackSegments),
JS_CFUNC_DEF("getRandom", 2, ScContext::getRandom),
JS_CFUNC_DEF("formatString", 0, ScContext::formatString),
JS_CFUNC_DEF("subscribe", 2, ScContext::subscribe),
JS_CFUNC_DEF("queryAction", 3, ScContext::queryAction),
JS_CFUNC_DEF("executeAction", 3, ScContext::executeAction),
JS_CFUNC_DEF("registerAction", 3, ScContext::registerAction),
JS_CFUNC_DEF("setInterval", 2, ScContext::setInterval),
JS_CFUNC_DEF("setTimeout", 2, ScContext::setTimeout),
JS_CFUNC_DEF("clearInterval", 1, ScContext::clearInterval),
JS_CFUNC_DEF("clearTimeout", 1, ScContext::clearTimeout),
JS_CFUNC_DEF("getIcon", 1, ScContext::getIcon),
};
return MakeWithOpaque(ctx, funcs, nullptr);
}
};
uint32_t ImageFromDuk(const DukValue& d)
{
uint32_t img{};
if (d.type() == DukValue::Type::NUMBER)
{
img = d.as_uint();
if (GetTargetAPIVersion() <= kApiVersionG2Reorder)
{
img = NewIconIndex(d.as_uint());
}
}
else if (d.type() == DukValue::Type::STRING)
{
img = GetIconByName(d.as_c_string());
}
return img;
}
} // namespace OpenRCT2::Scripting
#endif
@@ -11,34 +11,47 @@
#ifdef ENABLE_SCRIPTING
#include "../../Duktape.hpp"
#include <functional>
namespace OpenRCT2::Scripting
{
class ScDisposable
class ScDisposable;
extern ScDisposable gScDisposable;
class ScDisposable final : public ScBase
{
private:
std::function<void()> _onDispose;
using OpaqueType = std::function<void()>;
public:
ScDisposable(const std::function<void()>& onDispose)
: _onDispose(onDispose)
static JSValue dispose(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
}
void dispose() const
{
if (_onDispose)
OpaqueType* data = gScDisposable.GetOpaque<OpaqueType*>(thisVal);
if (data && *data)
{
_onDispose();
(*data)();
}
return JS_UNDEFINED;
}
static void Register(duk_context* ctx)
JSValue New(JSContext* ctx, const std::function<void()>& f)
{
dukglue_register_method(ctx, &ScDisposable::dispose, "dispose");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CFUNC_DEF("dispose", 0, ScDisposable::dispose),
};
return MakeWithOpaque(ctx, funcs, f ? new OpaqueType(f) : nullptr);
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Disposable", Finalize);
}
static void Finalize(JSRuntime* rt, JSValue thisVal)
{
OpaqueType* data = gScDisposable.GetOpaque<OpaqueType*>(thisVal);
if (data)
delete data;
}
};
} // namespace OpenRCT2::Scripting
@@ -11,74 +11,74 @@
#ifdef ENABLE_SCRIPTING
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
#include "../game/ScContext.hpp"
namespace OpenRCT2::Scripting
{
class ScPlugin
class ScPlugin;
extern ScPlugin gScPlugin;
class ScPlugin final : public ScBase
{
public:
static void Register(duk_context* ctx)
{
dukglue_register_property(ctx, &ScPlugin::plugins_get, nullptr, "plugins");
}
private:
std::vector<DukValue> plugins_get()
{
auto ctx = getContext();
auto& allPlugins = getallPlugins();
return formatMetadata(ctx, allPlugins);
}
duk_context* getContext()
{
// Get the context from the script engine
ScriptEngine& scriptEngine = GetContext()->GetScriptEngine();
return scriptEngine.GetContext();
}
const std::vector<std::shared_ptr<Plugin>> getallPlugins()
static const std::vector<std::shared_ptr<Plugin>> getallPlugins()
{
// Get all of the plugins from the script engine
ScriptEngine& scriptEngine = GetContext()->GetScriptEngine();
return scriptEngine.GetPlugins();
}
const std::vector<DukValue> formatMetadata(duk_context* ctx, const std::vector<std::shared_ptr<Plugin>>& allPlugins)
static JSValue plugins_get(JSContext* ctx, JSValue)
{
std::vector<DukValue> formattedMetadata;
duk_idx_t dukIdx = DUK_INVALID_INDEX;
// Iterate through all plugins and and cast their data to Duk objects
auto& allPlugins = getallPlugins();
return formatMetadata(ctx, allPlugins);
}
static JSValue formatMetadata(
JSContext* ctx, const std::vector<std::shared_ptr<OpenRCT2::Scripting::Plugin>>& allPlugins)
{
JSValue formattedMetadata = JS_NewArray(ctx);
// Iterate through all plugins and and cast their data to JSValue objects
int64_t index = 0;
for (const auto& pluginPtr : allPlugins)
{
// Pull out metadata
Plugin& plugin = *pluginPtr;
PluginMetadata metadata = plugin.GetMetadata();
// Create object using Duk stack
dukIdx = duk_push_object(ctx);
// Create object using context
JSValue val = JS_NewObject(ctx);
// Name and Version
duk_push_string(ctx, metadata.Name.c_str());
duk_put_prop_string(ctx, dukIdx, "name");
duk_push_string(ctx, metadata.Version.c_str());
duk_put_prop_string(ctx, dukIdx, "version");
JS_SetPropertyStr(ctx, val, "name", JSFromStdString(ctx, metadata.Name));
JS_SetPropertyStr(ctx, val, "version", JSFromStdString(ctx, metadata.Version));
// Authors
duk_idx_t arrIdx = duk_push_array(ctx);
for (auto [s, idx] = std::tuple{ metadata.Authors.begin(), 0 }; s != metadata.Authors.end(); s++, idx++)
JSValue authorsArray = JS_NewArray(ctx);
int64_t idx = 0;
for (auto& str : metadata.Authors)
{
auto& str = *s;
duk_push_string(ctx, str.c_str());
duk_put_prop_index(ctx, arrIdx, idx);
JSValue authorStr = JSFromStdString(ctx, str);
JS_SetPropertyInt64(ctx, authorsArray, idx++, authorStr);
}
duk_put_prop_string(ctx, dukIdx, "authors");
// Take from Duk stack
formattedMetadata.push_back(DukValue::take_from_stack(ctx, dukIdx));
dukIdx = DUK_INVALID_INDEX;
JS_SetPropertyStr(ctx, val, "authors", authorsArray);
JS_SetPropertyInt64(ctx, formattedMetadata, index++, val);
}
return formattedMetadata;
}
public:
JSValue New(JSContext* ctx)
{
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("plugins", ScPlugin::plugins_get, nullptr),
};
return MakeWithOpaque(ctx, funcs, nullptr);
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "PluginManager");
}
};
} // namespace OpenRCT2::Scripting
@@ -12,92 +12,92 @@
#ifdef ENABLE_SCRIPTING
#include "../../../profiling/Profiling.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
namespace OpenRCT2::Scripting
{
class ScProfiler
class ScProfiler;
extern ScProfiler gScProfiler;
class ScProfiler final : public ScBase
{
private:
duk_context* _ctx{};
public:
ScProfiler(duk_context* ctx)
: _ctx(ctx)
static JSValue GetFunctionIndexArray(
JSContext* ctx, const std::vector<Profiling::Function*>& all, const std::vector<Profiling::Function*>& items)
{
}
private:
DukValue getData()
{
const auto& data = Profiling::getData();
duk_push_array(_ctx);
duk_uarridx_t index = 0;
for (const auto& f : data)
{
DukObject obj(_ctx);
obj.Set("name", f->getName());
obj.Set("callCount", f->getCallCount());
obj.Set("minTime", f->getMinTime());
obj.Set("maxTime", f->getMaxTime());
obj.Set("totalTime", f->getTotalTime());
obj.Set("averageTime", f->getAverageTime());
obj.Set("parents", GetFunctionIndexArray(data, f->getParents()));
obj.Set("children", GetFunctionIndexArray(data, f->getChildren()));
obj.Take().push();
duk_put_prop_index(_ctx, /* duk stack index */ -2, index);
index++;
}
return DukValue::take_from_stack(_ctx);
}
DukValue GetFunctionIndexArray(
const std::vector<Profiling::Function*>& all, const std::vector<Profiling::Function*>& items)
{
duk_push_array(_ctx);
duk_uarridx_t index = 0;
JSValue functionArray = JS_NewArray(ctx);
int64_t index = 0;
for (const auto& item : items)
{
auto it = std::find(all.begin(), all.end(), item);
if (it != all.end())
{
auto value = static_cast<duk_int_t>(std::distance(all.begin(), it));
duk_push_int(_ctx, value);
duk_put_prop_index(_ctx, /* duk stack index */ -2, index);
index++;
auto value = static_cast<uint64_t>(std::distance(all.begin(), it));
JS_SetPropertyInt64(ctx, functionArray, index++, JS_NewInt64(ctx, value));
}
}
return DukValue::take_from_stack(_ctx);
return functionArray;
}
void start()
static JSValue getData(JSContext* ctx, JSValue, int, JSValue*)
{
const auto& data = Profiling::getData();
JSValue profileData = JS_NewArray(ctx);
int64_t index = 0;
for (const auto& f : data)
{
JSValue val = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, val, "name", JSFromStdString(ctx, f->getName()));
JS_SetPropertyStr(ctx, val, "callCount", JS_NewInt64(ctx, f->getCallCount()));
JS_SetPropertyStr(ctx, val, "minTime", JS_NewFloat64(ctx, f->getMinTime()));
JS_SetPropertyStr(ctx, val, "maxTime", JS_NewFloat64(ctx, f->getMaxTime()));
JS_SetPropertyStr(ctx, val, "totalTime", JS_NewFloat64(ctx, f->getTotalTime()));
JS_SetPropertyStr(ctx, val, "averageTime", JS_NewFloat64(ctx, f->getAverageTime()));
JS_SetPropertyStr(ctx, val, "parents", GetFunctionIndexArray(ctx, data, f->getParents()));
JS_SetPropertyStr(ctx, val, "children", GetFunctionIndexArray(ctx, data, f->getChildren()));
JS_SetPropertyInt64(ctx, profileData, index++, val);
}
return profileData;
}
static JSValue start(JSContext*, JSValue, int, JSValue*)
{
Profiling::enable();
return JS_UNDEFINED;
}
void stop()
static JSValue stop(JSContext*, JSValue, int, JSValue*)
{
Profiling::disable();
return JS_UNDEFINED;
}
void reset()
static JSValue reset(JSContext*, JSValue, int, JSValue*)
{
Profiling::resetData();
return JS_UNDEFINED;
}
bool enabled_get() const
static JSValue enabled_get(JSContext* ctx, JSValue)
{
return Profiling::isEnabled();
return JS_NewBool(ctx, Profiling::isEnabled());
}
public:
static void Register(duk_context* ctx)
JSValue New(JSContext* ctx)
{
dukglue_register_method(ctx, &ScProfiler::getData, "getData");
dukglue_register_method(ctx, &ScProfiler::start, "start");
dukglue_register_method(ctx, &ScProfiler::stop, "stop");
dukglue_register_method(ctx, &ScProfiler::reset, "reset");
dukglue_register_property(ctx, &ScProfiler::enabled_get, nullptr, "enabled");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CFUNC_DEF("getData", 0, ScProfiler::getData),
JS_CFUNC_DEF("start", 0, ScProfiler::start),
JS_CFUNC_DEF("stop", 0, ScProfiler::stop),
JS_CFUNC_DEF("reset", 0, ScProfiler::reset),
JS_CGETSET_DEF("enabled", ScProfiler::enabled_get, nullptr),
};
return MakeWithOpaque(ctx, funcs, nullptr);
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Profiler");
}
};
} // namespace OpenRCT2::Scripting
@@ -21,103 +21,102 @@
namespace OpenRCT2::Scripting
{
ScNetwork::ScNetwork(duk_context* ctx)
: _context(ctx)
{
}
std::string ScNetwork::mode_get() const
JSValue ScNetwork::mode_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
switch (Network::GetMode())
{
case Network::Mode::server:
return "server";
return JSFromStdString(ctx, "server");
case Network::Mode::client:
return "client";
return JSFromStdString(ctx, "client");
default:
break;
}
#endif
return "none";
return JSFromStdString(ctx, "none");
}
int32_t ScNetwork::numPlayers_get() const
JSValue ScNetwork::numPlayers_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
return Network::GetNumPlayers();
return JS_NewInt32(ctx, Network::GetNumPlayers());
#else
return 0;
return JS_NewInt32(ctx, 0);
#endif
}
int32_t ScNetwork::numGroups_get() const
JSValue ScNetwork::numGroups_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
return Network::GetNumGroups();
return JS_NewInt32(ctx, Network::GetNumGroups());
#else
return 0;
return JS_NewInt32(ctx, 0);
#endif
}
int32_t ScNetwork::defaultGroup_get() const
JSValue ScNetwork::defaultGroup_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
return Network::GetDefaultGroup();
return JS_NewInt32(ctx, Network::GetDefaultGroup());
#else
return 0;
return JS_NewInt32(ctx, 0);
#endif
}
void ScNetwork::defaultGroup_set(int32_t value)
JSValue ScNetwork::defaultGroup_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
#ifndef DISABLE_NETWORK
auto action = GameActions::NetworkModifyGroupAction(GameActions::ModifyGroupType::SetDefault, value);
JS_UNPACK_INT32(valueInt, ctx, value);
auto action = GameActions::NetworkModifyGroupAction(GameActions::ModifyGroupType::SetDefault, valueInt);
GameActions::Execute(&action, getGameState());
#endif
return JS_UNDEFINED;
}
std::vector<std::shared_ptr<ScPlayerGroup>> ScNetwork::groups_get() const
JSValue ScNetwork::groups_get(JSContext* ctx, JSValue thisVal)
{
std::vector<std::shared_ptr<ScPlayerGroup>> groups;
JSValue groups = JS_NewArray(ctx);
#ifndef DISABLE_NETWORK
auto numGroups = Network::GetNumGroups();
for (int32_t i = 0; i < numGroups; i++)
{
auto groupId = Network::GetGroupID(i);
groups.push_back(std::make_shared<ScPlayerGroup>(groupId));
JS_SetPropertyInt64(ctx, groups, i, gScPlayerGroup.New(ctx, groupId));
}
#endif
return groups;
}
std::vector<std::shared_ptr<ScPlayer>> ScNetwork::players_get() const
JSValue ScNetwork::players_get(JSContext* ctx, JSValue thisVal)
{
std::vector<std::shared_ptr<ScPlayer>> players;
JSValue players = JS_NewArray(ctx);
#ifndef DISABLE_NETWORK
auto numPlayers = Network::GetNumPlayers();
for (int32_t i = 0; i < numPlayers; i++)
{
auto playerId = Network::GetPlayerID(i);
players.push_back(std::make_shared<ScPlayer>(playerId));
JS_SetPropertyInt64(ctx, players, i, gScPlayer.New(ctx, playerId));
}
#endif
return players;
}
std::shared_ptr<ScPlayer> ScNetwork::currentPlayer_get() const
JSValue ScNetwork::currentPlayer_get(JSContext* ctx, JSValue thisVal)
{
std::shared_ptr<ScPlayer> player;
#ifndef DISABLE_NETWORK
auto playerId = Network::GetCurrentPlayerId();
player = std::make_shared<ScPlayer>(playerId);
return gScPlayer.New(ctx, playerId);
#else
return JS_NULL;
#endif
return player;
}
std::shared_ptr<ScPlayer> ScNetwork::getPlayer(int32_t id) const
JSValue ScNetwork::getPlayer(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
#ifndef DISABLE_NETWORK
JS_UNPACK_INT32(id, ctx, argv[0]);
if (GetTargetAPIVersion() < kApiVersionNetworkIDs)
{
auto index = id;
@@ -125,7 +124,7 @@ namespace OpenRCT2::Scripting
if (index < numPlayers)
{
auto playerId = Network::GetPlayerID(index);
return std::make_shared<ScPlayer>(playerId);
return gScPlayer.New(ctx, playerId);
}
}
else
@@ -133,50 +132,49 @@ namespace OpenRCT2::Scripting
auto index = Network::GetPlayerIndex(id);
if (index != -1)
{
return std::make_shared<ScPlayer>(id);
return gScPlayer.New(ctx, id);
}
}
#endif
return nullptr;
return JS_NULL;
}
DukValue ScNetwork::stats_get() const
JSValue ScNetwork::stats_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
auto obj = DukObject(_context);
JSValue obj = JS_NewObject(ctx);
auto networkStats = Network::GetStats();
{
duk_push_array(_context);
duk_uarridx_t index = 0;
for (auto v : networkStats.bytesReceived)
JSValue recvStatsArr = JS_NewArray(ctx);
uint32_t index = 0;
for (const auto v : networkStats.bytesReceived)
{
duk_push_number(_context, v);
duk_put_prop_index(_context, -2, index);
JS_SetPropertyUint32(ctx, recvStatsArr, index, JS_NewInt64(ctx, static_cast<int64_t>(v)));
index++;
}
obj.Set("bytesReceived", DukValue::take_from_stack(_context));
JS_SetPropertyStr(ctx, obj, "bytesReceived", recvStatsArr);
}
{
duk_push_array(_context);
duk_uarridx_t index = 0;
JSValue sentStatsArr = JS_NewArray(ctx);
uint32_t index = 0;
for (auto v : networkStats.bytesSent)
{
duk_push_number(_context, v);
duk_put_prop_index(_context, -2, index);
JS_SetPropertyUint32(ctx, sentStatsArr, index, JS_NewInt64(ctx, static_cast<int64_t>(v)));
index++;
}
obj.Set("bytesSent", DukValue::take_from_stack(_context));
JS_SetPropertyStr(ctx, obj, "bytesSent", sentStatsArr);
}
return obj.Take();
return obj;
#else
return ToDuk(_context, nullptr);
return JS_NULL;
#endif
}
std::shared_ptr<ScPlayerGroup> ScNetwork::getGroup(int32_t id) const
JSValue ScNetwork::getGroup(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
#ifndef DISABLE_NETWORK
JS_UNPACK_INT32(id, ctx, argv[0])
if (GetTargetAPIVersion() < kApiVersionNetworkIDs)
{
auto index = id;
@@ -184,7 +182,7 @@ namespace OpenRCT2::Scripting
if (index < numGroups)
{
auto groupId = Network::GetGroupID(index);
return std::make_shared<ScPlayerGroup>(groupId);
return gScPlayerGroup.New(ctx, groupId);
}
}
else
@@ -192,24 +190,27 @@ namespace OpenRCT2::Scripting
auto index = Network::GetGroupIndex(id);
if (index != -1)
{
return std::make_shared<ScPlayerGroup>(id);
return gScPlayerGroup.New(ctx, id);
}
}
#endif
return nullptr;
return JS_NULL;
}
void ScNetwork::addGroup()
JSValue ScNetwork::addGroup(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
#ifndef DISABLE_NETWORK
auto networkModifyGroup = GameActions::NetworkModifyGroupAction(GameActions::ModifyGroupType::AddGroup);
GameActions::Execute(&networkModifyGroup, getGameState());
#endif
return JS_UNDEFINED;
}
void ScNetwork::removeGroup(int32_t id)
JSValue ScNetwork::removeGroup(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
#ifndef DISABLE_NETWORK
JS_UNPACK_INT32(id, ctx, argv[0]);
if (GetTargetAPIVersion() < kApiVersionNetworkIDs)
{
auto index = id;
@@ -231,11 +232,14 @@ namespace OpenRCT2::Scripting
}
}
#endif
return JS_UNDEFINED;
}
void ScNetwork::kickPlayer(int32_t id)
JSValue ScNetwork::kickPlayer(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
#ifndef DISABLE_NETWORK
JS_UNPACK_INT32(id, ctx, argv[0]);
if (GetTargetAPIVersion() < kApiVersionNetworkIDs)
{
auto index = id;
@@ -257,32 +261,32 @@ namespace OpenRCT2::Scripting
}
}
#endif
return JS_UNDEFINED;
}
void ScNetwork::sendMessage(std::string message, DukValue players)
JSValue ScNetwork::sendMessage(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
#ifndef DISABLE_NETWORK
if (players.is_array())
JS_UNPACK_STR(message, ctx, argv[0]);
JSValue players = argv[1];
if (JS_IsArray(players))
{
if (Network::GetMode() == Network::Mode::server)
{
std::vector<uint8_t> playerIds;
auto playerArray = players.as_array();
for (const auto& item : playerArray)
{
if (item.type() == DukValue::Type::NUMBER)
{
playerIds.push_back(static_cast<uint8_t>(item.as_uint()));
}
}
if (!playerArray.empty())
JSIterateArray(ctx, players, [&playerIds](JSContext* ctx2, JSValue val) {
playerIds.push_back(static_cast<uint8_t>(JSToInt(ctx2, val)));
});
if (!playerIds.empty())
{
Network::SendChat(message.c_str(), playerIds);
}
}
else
{
duk_error(players.context(), DUK_ERR_ERROR, "Only servers can send private messages.");
JS_ThrowPlainError(ctx, "Only servers can send private messages.");
return JS_EXCEPTION;
}
}
else
@@ -290,59 +294,57 @@ namespace OpenRCT2::Scripting
Network::SendChat(message.c_str());
}
#endif
return JS_UNDEFINED;
}
#ifndef DISABLE_NETWORK
std::shared_ptr<ScListener> ScNetwork::createListener()
JSValue ScNetwork::createListener(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
auto socket = std::make_shared<ScListener>(plugin);
scriptEngine.AddSocket(socket);
return socket;
return gScListener.New(ctx, GetContext()->GetScriptEngine().GetExecInfo().GetCurrentPlugin());
}
#else
void ScNetwork::createListener()
JSValue ScNetwork::createListener(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
duk_error(_context, DUK_ERR_ERROR, "Networking has been disabled.");
return JS_ThrowPlainError(ctx, "Networking has been disabled.");
}
#endif
#ifndef DISABLE_NETWORK
std::shared_ptr<ScSocket> ScNetwork::createSocket()
JSValue ScNetwork::createSocket(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
auto socket = std::make_shared<ScSocket>(plugin);
scriptEngine.AddSocket(socket);
return socket;
return gScSocket.New(ctx, GetContext()->GetScriptEngine().GetExecInfo().GetCurrentPlugin());
}
#else
void ScNetwork::createSocket()
JSValue ScNetwork::createSocket(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
duk_error(_context, DUK_ERR_ERROR, "Networking has been disabled.");
return JS_ThrowPlainError(ctx, "Networking has been disabled.");
}
#endif
void ScNetwork::Register(duk_context* ctx)
JSValue ScNetwork::New(JSContext* ctx)
{
dukglue_register_property(ctx, &ScNetwork::mode_get, nullptr, "mode");
dukglue_register_property(ctx, &ScNetwork::numGroups_get, nullptr, "numGroups");
dukglue_register_property(ctx, &ScNetwork::numPlayers_get, nullptr, "numPlayers");
dukglue_register_property(ctx, &ScNetwork::groups_get, nullptr, "groups");
dukglue_register_property(ctx, &ScNetwork::players_get, nullptr, "players");
dukglue_register_property(ctx, &ScNetwork::currentPlayer_get, nullptr, "currentPlayer");
dukglue_register_property(ctx, &ScNetwork::defaultGroup_get, &ScNetwork::defaultGroup_set, "defaultGroup");
dukglue_register_property(ctx, &ScNetwork::stats_get, nullptr, "stats");
dukglue_register_method(ctx, &ScNetwork::addGroup, "addGroup");
dukglue_register_method(ctx, &ScNetwork::getGroup, "getGroup");
dukglue_register_method(ctx, &ScNetwork::removeGroup, "removeGroup");
dukglue_register_method(ctx, &ScNetwork::getPlayer, "getPlayer");
dukglue_register_method(ctx, &ScNetwork::kickPlayer, "kickPlayer");
dukglue_register_method(ctx, &ScNetwork::sendMessage, "sendMessage");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("mode", ScNetwork::mode_get, nullptr),
JS_CGETSET_DEF("numGroups", ScNetwork::numGroups_get, nullptr),
JS_CGETSET_DEF("numPlayers", ScNetwork::numPlayers_get, nullptr),
JS_CGETSET_DEF("groups", ScNetwork::groups_get, nullptr),
JS_CGETSET_DEF("players", ScNetwork::players_get, nullptr),
JS_CGETSET_DEF("currentPlayer", ScNetwork::currentPlayer_get, nullptr),
JS_CGETSET_DEF("defaultGroup", ScNetwork::defaultGroup_get, ScNetwork::defaultGroup_set),
JS_CGETSET_DEF("stats", ScNetwork::stats_get, nullptr),
dukglue_register_method(ctx, &ScNetwork::createListener, "createListener");
dukglue_register_method(ctx, &ScNetwork::createSocket, "createSocket");
JS_CFUNC_DEF("addGroup", 0, ScNetwork::addGroup),
JS_CFUNC_DEF("getGroup", 1, ScNetwork::getGroup),
JS_CFUNC_DEF("removeGroup", 1, ScNetwork::removeGroup),
JS_CFUNC_DEF("getPlayer", 1, ScNetwork::getPlayer),
JS_CFUNC_DEF("kickPlayer", 1, ScNetwork::kickPlayer),
JS_CFUNC_DEF("sendMessage", 2, ScNetwork::sendMessage),
JS_CFUNC_DEF("createListener", 0, ScNetwork::createListener),
JS_CFUNC_DEF("createSocket", 0, ScNetwork::createSocket)
};
return MakeWithOpaque(ctx, funcs, nullptr);
}
} // namespace OpenRCT2::Scripting
@@ -11,7 +11,7 @@
#ifdef ENABLE_SCRIPTING
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
#include "ScPlayer.hpp"
#include "ScPlayerGroup.hpp"
#include "ScSocket.hpp"
@@ -20,57 +20,48 @@
namespace OpenRCT2::Scripting
{
class ScNetwork
class ScNetwork final : public ScBase
{
private:
#ifdef __clang__
[[maybe_unused]]
#endif
duk_context* _context;
static JSValue mode_get(JSContext* ctx, JSValue thisVal);
static JSValue numPlayers_get(JSContext* ctx, JSValue thisVal);
static JSValue numGroups_get(JSContext* ctx, JSValue thisVal);
static JSValue defaultGroup_get(JSContext* ctx, JSValue thisVal);
static JSValue defaultGroup_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue groups_get(JSContext* ctx, JSValue thisVal);
static JSValue players_get(JSContext* ctx, JSValue thisVal);
static JSValue currentPlayer_get(JSContext* ctx, JSValue thisVal);
static JSValue getPlayer(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue stats_get(JSContext* ctx, JSValue thisVal);
static JSValue getGroup(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue addGroup(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue removeGroup(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue kickPlayer(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue sendMessage(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue createListener(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue createSocket(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
public:
ScNetwork(duk_context* ctx);
JSValue New(JSContext* ctx);
std::string mode_get() const;
int32_t numPlayers_get() const;
int32_t numGroups_get() const;
int32_t defaultGroup_get() const;
void defaultGroup_set(int32_t value);
std::vector<std::shared_ptr<ScPlayerGroup>> groups_get() const;
std::vector<std::shared_ptr<ScPlayer>> players_get() const;
std::shared_ptr<ScPlayer> currentPlayer_get() const;
std::shared_ptr<ScPlayer> getPlayer(int32_t id) const;
DukValue stats_get() const;
std::shared_ptr<ScPlayerGroup> getGroup(int32_t id) const;
void addGroup();
void removeGroup(int32_t id);
void kickPlayer(int32_t id);
void sendMessage(std::string message, DukValue players);
#ifndef DISABLE_NETWORK
std::shared_ptr<ScListener> createListener();
#else
void createListener();
#endif
#ifndef DISABLE_NETWORK
std::shared_ptr<ScSocket> createSocket();
#else
void createSocket();
#endif
static void Register(duk_context* ctx);
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Network");
}
};
extern ScNetwork gScNetwork;
} // namespace OpenRCT2::Scripting
#endif
@@ -20,103 +20,128 @@
namespace OpenRCT2::Scripting
{
ScPlayer::ScPlayer(int32_t id)
: _id(id)
using OpaquePlayerData = struct
{
int32_t id;
};
int32_t ScPlayer::GetPlayerId(JSValue thisVal)
{
OpaquePlayerData* data = gScPlayer.GetOpaque<OpaquePlayerData*>(thisVal);
if (data)
return data->id;
return -1;
}
int32_t ScPlayer::id_get() const
JSValue ScPlayer::id_get(JSContext* ctx, JSValue thisVal)
{
return _id;
return JS_NewInt32(ctx, GetPlayerId(thisVal));
}
std::string ScPlayer::name_get() const
JSValue ScPlayer::name_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
auto index = Network::GetPlayerIndex(_id);
auto index = Network::GetPlayerIndex(GetPlayerId(thisVal));
if (index == -1)
return {};
return Network::GetPlayerName(index);
return JSFromStdString(ctx, {});
return JSFromStdString(ctx, Network::GetPlayerName(index));
#else
return {};
return JSFromStdString(ctx, {});
#endif
}
int32_t ScPlayer::group_get() const
JSValue ScPlayer::group_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
auto index = Network::GetPlayerIndex(_id);
auto index = Network::GetPlayerIndex(GetPlayerId(thisVal));
if (index == -1)
return {};
return Network::GetPlayerGroup(index);
return JS_NewInt32(ctx, {});
return JS_NewInt32(ctx, Network::GetPlayerGroup(index));
#else
return 0;
return JS_NewInt32(ctx, 0);
#endif
}
void ScPlayer::group_set(int32_t value)
JSValue ScPlayer::group_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
#ifndef DISABLE_NETWORK
auto playerSetGroupAction = GameActions::PlayerSetGroupAction(_id, value);
JS_UNPACK_INT32(valueInt, ctx, value)
auto playerSetGroupAction = GameActions::PlayerSetGroupAction(GetPlayerId(thisVal), valueInt);
GameActions::Execute(&playerSetGroupAction, getGameState());
#endif
return JS_UNDEFINED;
}
int32_t ScPlayer::ping_get() const
JSValue ScPlayer::ping_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
auto index = Network::GetPlayerIndex(_id);
auto index = Network::GetPlayerIndex(GetPlayerId(thisVal));
if (index == -1)
return {};
return Network::GetPlayerPing(index);
return JS_NewInt32(ctx, {});
return JS_NewInt32(ctx, Network::GetPlayerPing(index));
#else
return 0;
return JS_NewInt32(ctx, 0);
#endif
}
int32_t ScPlayer::commandsRan_get() const
JSValue ScPlayer::commandsRan_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
auto index = Network::GetPlayerIndex(_id);
auto index = Network::GetPlayerIndex(GetPlayerId(thisVal));
if (index == -1)
return {};
return Network::GetPlayerCommandsRan(index);
return JS_NewInt32(ctx, {});
return JS_NewInt32(ctx, Network::GetPlayerCommandsRan(index));
#else
return 0;
return JS_NewInt32(ctx, 0);
#endif
}
int32_t ScPlayer::moneySpent_get() const
JSValue ScPlayer::moneySpent_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
auto index = Network::GetPlayerIndex(_id);
auto index = Network::GetPlayerIndex(GetPlayerId(thisVal));
if (index == -1)
return {};
return Network::GetPlayerMoneySpent(index);
return JS_NewInt64(ctx, {});
return JS_NewInt64(ctx, Network::GetPlayerMoneySpent(index));
#else
return 0;
return JS_NewInt64(ctx, 0);
#endif
}
std::string ScPlayer::ipAddress_get() const
JSValue ScPlayer::ipAddress_get(JSContext* ctx, JSValue thisVal)
{
return Network::GetPlayerIPAddress(_id);
return JSFromStdString(ctx, Network::GetPlayerIPAddress(GetPlayerId(thisVal)));
}
std::string ScPlayer::publicKeyHash_get() const
JSValue ScPlayer::publicKeyHash_get(JSContext* ctx, JSValue thisVal)
{
return Network::GetPlayerPublicKeyHash(_id);
return JSFromStdString(ctx, Network::GetPlayerPublicKeyHash(GetPlayerId(thisVal)));
}
void ScPlayer::Register(duk_context* ctx)
JSValue ScPlayer::New(JSContext* ctx, int32_t id)
{
dukglue_register_property(ctx, &ScPlayer::id_get, nullptr, "id");
dukglue_register_property(ctx, &ScPlayer::name_get, nullptr, "name");
dukglue_register_property(ctx, &ScPlayer::group_get, &ScPlayer::group_set, "group");
dukglue_register_property(ctx, &ScPlayer::ping_get, nullptr, "ping");
dukglue_register_property(ctx, &ScPlayer::commandsRan_get, nullptr, "commandsRan");
dukglue_register_property(ctx, &ScPlayer::moneySpent_get, nullptr, "moneySpent");
dukglue_register_property(ctx, &ScPlayer::ipAddress_get, nullptr, "ipAddress");
dukglue_register_property(ctx, &ScPlayer::publicKeyHash_get, nullptr, "publicKeyHash");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("id", ScPlayer::id_get, nullptr),
JS_CGETSET_DEF("name", ScPlayer::name_get, nullptr),
JS_CGETSET_DEF("group", ScPlayer::group_get, ScPlayer::group_set),
JS_CGETSET_DEF("ping", ScPlayer::ping_get, nullptr),
JS_CGETSET_DEF("commandsRan", ScPlayer::commandsRan_get, nullptr),
JS_CGETSET_DEF("moneySpent", ScPlayer::moneySpent_get, nullptr),
JS_CGETSET_DEF("ipAddress", ScPlayer::ipAddress_get, nullptr),
JS_CGETSET_DEF("publicKeyHash", ScPlayer::publicKeyHash_get, nullptr),
};
return MakeWithOpaque(ctx, funcs, new OpaquePlayerData{ id });
}
void ScPlayer::Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Player", Finalize);
}
void ScPlayer::Finalize(JSRuntime* rt, JSValue thisVal)
{
OpaquePlayerData* data = gScPlayer.GetOpaque<OpaquePlayerData*>(thisVal);
if (data)
delete data;
}
} // namespace OpenRCT2::Scripting
@@ -11,38 +11,43 @@
#ifdef ENABLE_SCRIPTING
#include "../../Duktape.hpp"
#include <string.h>
#include "../../ScriptEngine.h"
#include "quickjs.h"
namespace OpenRCT2::Scripting
{
class ScPlayer
class ScPlayer;
extern ScPlayer gScPlayer;
class ScPlayer : public ScBase
{
private:
int32_t _id;
static int32_t GetPlayerId(JSValue thisVal);
static JSValue id_get(JSContext* ctx, JSValue thisVal);
static JSValue name_get(JSContext* ctx, JSValue thisVal);
static JSValue group_get(JSContext* ctx, JSValue thisVal);
static JSValue group_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue ping_get(JSContext* ctx, JSValue thisVal);
static JSValue commandsRan_get(JSContext* ctx, JSValue thisVal);
static JSValue moneySpent_get(JSContext* ctx, JSValue thisVal);
static JSValue ipAddress_get(JSContext* ctx, JSValue thisVal);
static JSValue publicKeyHash_get(JSContext* ctx, JSValue thisVal);
public:
ScPlayer(int32_t id);
JSValue New(JSContext* ctx, int32_t id);
int32_t id_get() const;
void Register(JSContext* ctx);
std::string name_get() const;
int32_t group_get() const;
void group_set(int32_t value);
int32_t ping_get() const;
int32_t commandsRan_get() const;
int32_t moneySpent_get() const;
std::string ipAddress_get() const;
std::string publicKeyHash_get() const;
static void Register(duk_context* ctx);
private:
static void Finalize(JSRuntime* rt, JSValue thisVal);
};
} // namespace OpenRCT2::Scripting
@@ -19,98 +19,112 @@
#include "../../../core/String.hpp"
#include "../../../network/Network.h"
#include "../../../network/NetworkAction.h"
#include "../../Duktape.hpp"
namespace OpenRCT2::Scripting
{
ScPlayerGroup::ScPlayerGroup(int32_t id)
: _id(id)
using OpaquePlayerGroupData = struct
{
int32_t id;
};
int32_t ScPlayerGroup::GetGroupId(JSValue thisVal)
{
OpaquePlayerGroupData* data = gScPlayerGroup.GetOpaque<OpaquePlayerGroupData*>(thisVal);
if (data)
return data->id;
return -1;
}
int32_t ScPlayerGroup::id_get()
JSValue ScPlayerGroup::id_get(JSContext* ctx, JSValue thisVal)
{
return _id;
return JS_NewInt32(ctx, GetGroupId(thisVal));
}
std::string ScPlayerGroup::name_get() const
JSValue ScPlayerGroup::name_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
auto index = Network::GetGroupIndex(_id);
auto index = Network::GetGroupIndex(GetGroupId(thisVal));
if (index == -1)
return {};
return Network::GetGroupName(index);
return JSFromStdString(ctx, {});
return JSFromStdString(ctx, Network::GetGroupName(index));
#else
return {};
return JSFromStdString(ctx, {});
#endif
}
void ScPlayerGroup::name_set(std::string value)
JSValue ScPlayerGroup::name_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
#ifndef DISABLE_NETWORK
auto action = GameActions::NetworkModifyGroupAction(GameActions::ModifyGroupType::SetName, _id, value);
JS_UNPACK_STR(valueStr, ctx, value);
auto action = GameActions::NetworkModifyGroupAction(
GameActions::ModifyGroupType::SetName, GetGroupId(thisVal), valueStr);
GameActions::Execute(&action, getGameState());
#endif
return JS_UNDEFINED;
}
#ifndef DISABLE_NETWORK
static std::string TransformPermissionKeyToJS(const std::string& s)
static JSValue TransformPermissionKeyToJS(JSContext* ctx, const std::string& s)
{
auto result = s.substr(sizeof("PERMISSION_") - 1);
for (auto& c : result)
{
c = std::tolower(static_cast<unsigned char>(c));
}
return result;
return JSFromStdString(ctx, result);
}
static std::string TransformPermissionKeyToInternal(const std::string& s)
static std::string TransformPermissionKeyToInternal(JSContext* ctx, JSValue s)
{
return "PERMISSION_" + String::toUpper(s);
if (JS_IsString(s))
return "PERMISSION_" + String::toUpper(JSToStdString(ctx, s));
return std::string();
}
#endif
std::vector<std::string> ScPlayerGroup::permissions_get() const
JSValue ScPlayerGroup::permissions_get(JSContext* ctx, JSValue thisVal)
{
#ifndef DISABLE_NETWORK
auto index = Network::GetGroupIndex(_id);
if (index == -1)
return {};
// Create array of permissions
std::vector<std::string> result;
JSValue result = JS_NewArray(ctx);
#ifndef DISABLE_NETWORK
auto index = Network::GetGroupIndex(GetGroupId(thisVal));
if (index == -1)
return result;
auto permissionIndex = 0;
int64_t resultIdx = 0;
for (const auto& action : Network::NetworkActions::Actions)
{
if (Network::CanPerformAction(index, static_cast<Network::Permission>(permissionIndex)))
{
result.push_back(TransformPermissionKeyToJS(action.PermissionName));
JS_SetPropertyInt64(ctx, result, resultIdx++, TransformPermissionKeyToJS(ctx, action.PermissionName));
}
permissionIndex++;
}
return result;
#else
return {};
#endif
return result;
}
void ScPlayerGroup::permissions_set(std::vector<std::string> value)
JSValue ScPlayerGroup::permissions_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
#ifndef DISABLE_NETWORK
auto groupIndex = Network::GetGroupIndex(_id);
JS_UNPACK_ARRAY(array, ctx, value);
int32_t id = GetGroupId(thisVal);
auto groupIndex = Network::GetGroupIndex(id);
if (groupIndex == -1)
return;
return JS_UNDEFINED;
// First clear all permissions
auto networkAction = GameActions::NetworkModifyGroupAction(
GameActions::ModifyGroupType::SetPermissions, _id, "", 0, GameActions::PermissionState::ClearAll);
GameActions::ModifyGroupType::SetPermissions, id, "", 0, GameActions::PermissionState::ClearAll);
GameActions::Execute(&networkAction, getGameState());
std::vector<bool> enabledPermissions;
enabledPermissions.resize(Network::NetworkActions::Actions.size());
for (const auto& p : value)
{
auto permissionName = TransformPermissionKeyToInternal(p);
// Don't use vector<bool> since the weird bitpacking specialisation does not work with the lambda (on some compilers)
std::vector<uint8_t> enabledPermissions(Network::NetworkActions::Actions.size());
JSIterateArray(ctx, array, [&enabledPermissions](JSContext* ctx2, JSValue x) {
auto permissionName = TransformPermissionKeyToInternal(ctx2, x);
auto permissionIndex = 0;
for (const auto& action : Network::NetworkActions::Actions)
@@ -121,7 +135,7 @@ namespace OpenRCT2::Scripting
}
permissionIndex++;
}
}
});
for (size_t i = 0; i < enabledPermissions.size(); i++)
{
@@ -130,19 +144,35 @@ namespace OpenRCT2::Scripting
if (toggle)
{
auto networkAction2 = GameActions::NetworkModifyGroupAction(
GameActions::ModifyGroupType::SetPermissions, _id, "", static_cast<uint32_t>(i),
GameActions::ModifyGroupType::SetPermissions, id, "", static_cast<uint32_t>(i),
GameActions::PermissionState::Toggle);
GameActions::Execute(&networkAction2, getGameState());
}
}
#endif
return JS_UNDEFINED;
}
void ScPlayerGroup::Register(duk_context* ctx)
JSValue ScPlayerGroup::New(JSContext* ctx, int32_t id)
{
dukglue_register_property(ctx, &ScPlayerGroup::id_get, nullptr, "id");
dukglue_register_property(ctx, &ScPlayerGroup::name_get, &ScPlayerGroup::name_set, "name");
dukglue_register_property(ctx, &ScPlayerGroup::permissions_get, &ScPlayerGroup::permissions_set, "permissions");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("id", ScPlayerGroup::id_get, nullptr),
JS_CGETSET_DEF("name", ScPlayerGroup::name_get, ScPlayerGroup::name_set),
JS_CGETSET_DEF("permissions", ScPlayerGroup::permissions_get, ScPlayerGroup::permissions_set),
};
return MakeWithOpaque(ctx, funcs, new OpaquePlayerGroupData{ id });
}
void ScPlayerGroup::Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "PlayerGroup", Finalize);
}
void ScPlayerGroup::Finalize(JSRuntime* rt, JSValue thisVal)
{
OpaquePlayerGroupData* data = gScPlayerGroup.GetOpaque<OpaquePlayerGroupData*>(thisVal);
if (data)
delete data;
}
} // namespace OpenRCT2::Scripting
@@ -11,29 +11,33 @@
#ifdef ENABLE_SCRIPTING
#include "../../Duktape.hpp"
#include <string>
#include <vector>
#include "../../ScriptEngine.h"
namespace OpenRCT2::Scripting
{
class ScPlayerGroup
class ScPlayerGroup;
extern ScPlayerGroup gScPlayerGroup;
class ScPlayerGroup : public ScBase
{
int32_t _id;
private:
static int32_t GetGroupId(JSValue thisVal);
static JSValue id_get(JSContext* ctx, JSValue thisVal);
static JSValue name_get(JSContext* ctx, JSValue thisVal);
static JSValue name_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue permissions_get(JSContext* ctx, JSValue thisVal);
static JSValue permissions_set(JSContext* ctx, JSValue thisVal, JSValue value);
public:
ScPlayerGroup(int32_t id);
JSValue New(JSContext* ctx, int32_t id);
int32_t id_get();
void Register(JSContext* ctx);
std::string name_get() const;
void name_set(std::string value);
std::vector<std::string> permissions_get() const;
void permissions_set(std::vector<std::string> value);
static void Register(duk_context* ctx);
private:
static void Finalize(JSRuntime* rt, JSValue thisVal);
};
} // namespace OpenRCT2::Scripting
@@ -15,7 +15,6 @@
#include "../../../Context.h"
#include "../../../config/Config.h"
#include "../../../network/Socket.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
#include <memory>
@@ -26,9 +25,9 @@ namespace OpenRCT2::Scripting
class EventList
{
private:
std::vector<std::vector<DukValue>> _listeners;
std::vector<std::vector<JSCallback>> _listeners;
std::vector<DukValue>& GetListenerList(uint32_t id)
std::vector<JSCallback>& GetListenerList(uint32_t id)
{
if (_listeners.size() <= id)
{
@@ -39,7 +38,7 @@ namespace OpenRCT2::Scripting
public:
void Raise(
uint32_t id, const std::shared_ptr<Plugin>& plugin, const std::vector<DukValue>& args, bool isGameStateMutable)
uint32_t id, const std::shared_ptr<Plugin>& plugin, const std::vector<JSValue>& args, bool isGameStateMutable)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
@@ -47,23 +46,26 @@ namespace OpenRCT2::Scripting
auto listeners = GetListenerList(id);
for (size_t i = 0; i < listeners.size(); i++)
{
scriptEngine.ExecutePluginCall(plugin, listeners[i], args, isGameStateMutable);
scriptEngine.ExecutePluginCall(plugin, listeners[i].callback, args, isGameStateMutable);
// Safety, listeners might get reallocated
listeners = GetListenerList(id);
}
}
void AddListener(uint32_t id, const DukValue& listener)
void AddListener(uint32_t id, const JSCallback& listener)
{
auto& listeners = GetListenerList(id);
listeners.push_back(listener);
}
void RemoveListener(uint32_t id, const DukValue& value)
void RemoveListener(uint32_t id, const JSCallback& value)
{
auto& listeners = GetListenerList(id);
listeners.erase(std::remove(listeners.begin(), listeners.end(), value), listeners.end());
std::erase_if(listeners, [&value](const JSCallback& x) {
// Note: this might be hacky because I am not sure if/when quickjs will keep these function ptrs stable.
return JS_VALUE_GET_PTR(value.callback) == JS_VALUE_GET_PTR(x.callback);
});
}
void RemoveAllListeners(uint32_t id)
@@ -71,204 +73,61 @@ namespace OpenRCT2::Scripting
auto& listeners = GetListenerList(id);
listeners.clear();
}
void RemoveAllListeners()
{
_listeners.clear();
}
};
class ScSocketBase
inline bool IsLocalhostAddress(std::string_view s)
{
private:
std::shared_ptr<Plugin> _plugin;
return s == "localhost" || s == "127.0.0.1" || s == "::";
}
protected:
static bool IsLocalhostAddress(std::string_view s)
inline bool IsOnWhiteList(std::string_view host)
{
constexpr char delimiter = ',';
size_t start_pos = 0;
size_t end_pos = 0;
while ((end_pos = Config::Get().plugin.allowedHosts.find(delimiter, start_pos)) != std::string::npos)
{
return s == "localhost" || s == "127.0.0.1" || s == "::";
}
static bool IsOnWhiteList(std::string_view host)
{
constexpr char delimiter = ',';
size_t start_pos = 0;
size_t end_pos = 0;
while ((end_pos = Config::Get().plugin.allowedHosts.find(delimiter, start_pos)) != std::string::npos)
if (host == Config::Get().plugin.allowedHosts.substr(start_pos, end_pos - start_pos))
{
if (host == Config::Get().plugin.allowedHosts.substr(start_pos, end_pos - start_pos))
{
return true;
}
start_pos = end_pos + 1;
return true;
}
return host
== Config::Get().plugin.allowedHosts.substr(start_pos, Config::Get().plugin.allowedHosts.length() - start_pos);
start_pos = end_pos + 1;
}
return host
== Config::Get().plugin.allowedHosts.substr(start_pos, Config::Get().plugin.allowedHosts.length() - start_pos);
}
public:
ScSocketBase(const std::shared_ptr<Plugin>& plugin)
: _plugin(plugin)
{
}
virtual ~ScSocketBase()
{
Dispose();
}
const std::shared_ptr<Plugin>& GetPlugin() const
{
return _plugin;
}
struct SocketDataBase
{
EventList _eventList;
std::unique_ptr<Network::ITcpSocket> _socket;
std::shared_ptr<Plugin> _plugin;
bool _disposed{};
virtual ~SocketDataBase() = default;
virtual void Update() = 0;
virtual void Dispose()
{
}
virtual bool IsDisposed() const = 0;
virtual void Dispose() = 0;
};
class ScSocket final : public ScSocketBase
struct SocketData final : SocketDataBase
{
private:
static constexpr uint32_t EVENT_NONE = std::numeric_limits<uint32_t>::max();
static constexpr uint32_t EVENT_CLOSE = 0;
static constexpr uint32_t EVENT_DATA = 1;
static constexpr uint32_t EVENT_CONNECT_ONCE = 2;
static constexpr uint32_t EVENT_ERROR = 3;
EventList _eventList;
std::unique_ptr<Network::ITcpSocket> _socket;
bool _disposed{};
bool _connecting{};
bool _wasConnected{};
public:
ScSocket(const std::shared_ptr<Plugin>& plugin)
: ScSocketBase(plugin)
~SocketData() override
{
}
ScSocket(const std::shared_ptr<Plugin>& plugin, std::unique_ptr<Network::ITcpSocket>&& socket)
: ScSocketBase(plugin)
, _socket(std::move(socket))
{
}
private:
ScSocket* destroy(const DukValue& error)
{
CloseSocket();
return this;
}
ScSocket* setNoDelay(bool noDelay)
{
if (_socket != nullptr)
{
_socket->SetNoDelay(noDelay);
}
return this;
}
ScSocket* connect(uint16_t port, const std::string& host, const DukValue& callback)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
if (_socket != nullptr)
{
duk_error(ctx, DUK_ERR_ERROR, "Socket has already been created.");
}
else if (_disposed)
{
duk_error(ctx, DUK_ERR_ERROR, "Socket is disposed.");
}
else if (_connecting)
{
duk_error(ctx, DUK_ERR_ERROR, "Socket is already connecting.");
}
else if (!IsLocalhostAddress(host) && !IsOnWhiteList(host))
{
duk_error(ctx, DUK_ERR_ERROR, "For security reasons, only connecting to localhost is allowed.");
}
else
{
_socket = Network::CreateTcpSocket();
try
{
_socket->ConnectAsync(host, port);
_eventList.AddListener(EVENT_CONNECT_ONCE, callback);
_connecting = true;
}
catch (const std::exception& e)
{
duk_error(ctx, DUK_ERR_ERROR, e.what());
}
}
return this;
}
ScSocket* end(const DukValue& data)
{
if (_disposed)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
duk_error(ctx, DUK_ERR_ERROR, "Socket is disposed.");
}
else if (_socket != nullptr)
{
if (data.type() == DukValue::Type::STRING)
{
write(data.as_string());
_socket->Finish();
}
else
{
_socket->Finish();
auto ctx = GetContext()->GetScriptEngine().GetContext();
duk_error(ctx, DUK_ERR_ERROR, "Only sending strings is currently supported.");
}
}
return this;
}
bool write(const std::string& data)
{
if (_disposed)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
duk_error(ctx, DUK_ERR_ERROR, "Socket is disposed.");
}
else if (_socket != nullptr)
{
try
{
auto sentBytes = _socket->SendData(data.c_str(), data.size());
return sentBytes != data.size();
}
catch (const std::exception&)
{
return false;
}
}
return false;
}
ScSocket* on(const std::string& eventType, const DukValue& callback)
{
auto eventId = GetEventType(eventType);
if (eventId != EVENT_NONE)
{
_eventList.AddListener(eventId, callback);
}
return this;
}
ScSocket* off(const std::string& eventType, const DukValue& callback)
{
auto eventId = GetEventType(eventType);
if (eventId != EVENT_NONE)
{
_eventList.RemoveListener(eventId, callback);
}
return this;
SocketData::Dispose();
}
void CloseSocket()
@@ -283,22 +142,22 @@ namespace OpenRCT2::Scripting
RaiseOnClose(false);
}
}
_eventList.RemoveAllListeners();
}
void RaiseOnClose(bool hadError)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
_eventList.Raise(EVENT_CLOSE, GetPlugin(), { ToDuk(ctx, hadError) }, false);
JSContext* ctx = GetContext()->GetScriptEngine().GetContext();
_eventList.Raise(EVENT_CLOSE, _plugin, { JS_NewBool(ctx, hadError) }, false);
}
void RaiseOnData(const std::string& data)
void RaiseOnData(std::string_view data)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
_eventList.Raise(EVENT_DATA, GetPlugin(), { ToDuk(ctx, data) }, false);
JSContext* ctx = GetContext()->GetScriptEngine().GetContext();
_eventList.Raise(EVENT_DATA, _plugin, { JSFromStdString(ctx, data) }, false);
}
uint32_t GetEventType(std::string_view name)
static uint32_t GetEventType(std::string_view name)
{
if (name == "close")
return EVENT_CLOSE;
@@ -309,7 +168,6 @@ namespace OpenRCT2::Scripting
return EVENT_NONE;
}
public:
void Update() override
{
if (_disposed)
@@ -324,22 +182,21 @@ namespace OpenRCT2::Scripting
{
_connecting = false;
_wasConnected = true;
_eventList.Raise(EVENT_CONNECT_ONCE, GetPlugin(), {}, false);
_eventList.Raise(EVENT_CONNECT_ONCE, _plugin, {}, false);
_eventList.RemoveAllListeners(EVENT_CONNECT_ONCE);
}
else if (status == Network::SocketStatus::closed)
{
_connecting = false;
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
JSContext* ctx = GetContext()->GetScriptEngine().GetContext();
auto err = _socket->GetError();
if (err == nullptr)
{
err = "";
}
auto dukErr = ToDuk(ctx, std::string_view(err));
_eventList.Raise(EVENT_ERROR, GetPlugin(), { dukErr }, true);
auto jsErr = JSFromStdString(ctx, std::string_view(err));
_eventList.Raise(EVENT_ERROR, _plugin, { jsErr }, true);
}
}
else if (status == Network::SocketStatus::connected)
@@ -350,7 +207,7 @@ namespace OpenRCT2::Scripting
switch (result)
{
case Network::ReadPacket::success:
RaiseOnData(std::string(buffer, bytesRead));
RaiseOnData(std::string_view(buffer, bytesRead));
break;
case Network::ReadPacket::noData:
break;
@@ -373,129 +230,235 @@ namespace OpenRCT2::Scripting
CloseSocket();
_disposed = true;
}
bool IsDisposed() const override
{
return _disposed;
}
static void Register(duk_context* ctx)
{
dukglue_register_method(ctx, &ScSocket::destroy, "destroy");
dukglue_register_method(ctx, &ScSocket::setNoDelay, "setNoDelay");
dukglue_register_method(ctx, &ScSocket::connect, "connect");
dukglue_register_method(ctx, &ScSocket::end, "end");
dukglue_register_method(ctx, &ScSocket::write, "write");
dukglue_register_method(ctx, &ScSocket::on, "on");
dukglue_register_method(ctx, &ScSocket::off, "off");
}
};
class ScListener final : public ScSocketBase
class ScSocket;
extern ScSocket gScSocket;
class ScSocket final : public ScBase
{
private:
static constexpr uint32_t EVENT_NONE = std::numeric_limits<uint32_t>::max();
static constexpr uint32_t EVENT_CONNECTION = 0;
EventList _eventList;
std::unique_ptr<Network::ITcpSocket> _socket;
std::vector<std::shared_ptr<ScSocket>> _scClientSockets;
bool _disposed{};
bool listening_get()
static SocketData* GetData(JSValue thisVal)
{
if (_socket != nullptr)
return gScSocket.GetOpaque<SocketData*>(thisVal);
}
static JSValue destroy(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid socket");
data->CloseSocket();
return JS_DupValue(ctx, thisVal);
}
static JSValue setNoDelay(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_BOOL(noDelay, ctx, argv[0]);
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid socket");
if (data->_socket != nullptr)
{
return _socket->GetStatus() == Network::SocketStatus::listening;
data->_socket->SetNoDelay(noDelay);
}
return false;
return JS_DupValue(ctx, thisVal);
}
ScListener* close()
static JSValue connect(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
CloseSocket();
return this;
}
JS_UNPACK_UINT32(port, ctx, argv[0]);
if (port & 0xFFFF0000)
return JS_ThrowRangeError(ctx, "Invalid port.");
JS_UNPACK_STR(host, ctx, argv[1]);
ScListener* listen(int32_t port, const DukValue& dukHost)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
if (_disposed)
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid socket");
if (data->_socket != nullptr)
{
duk_error(ctx, DUK_ERR_ERROR, "Socket is disposed.");
JS_ThrowPlainError(ctx, "Socket has already been created.");
}
else if (data->_disposed)
{
JS_ThrowPlainError(ctx, "Socket is disposed.");
}
else if (data->_connecting)
{
JS_ThrowPlainError(ctx, "Socket is already connecting.");
}
else if (!IsLocalhostAddress(host) && !IsOnWhiteList(host))
{
JS_ThrowPlainError(ctx, "For security reasons, only connecting to localhost is allowed.");
}
else
{
if (_socket == nullptr)
data->_socket = Network::CreateTcpSocket();
try
{
_socket = Network::CreateTcpSocket();
data->_socket->ConnectAsync(host, port);
if (JS_IsFunction(ctx, argv[2]))
{
data->_eventList.AddListener(SocketData::EVENT_CONNECT_ONCE, JSCallback(ctx, argv[2]));
}
data->_connecting = true;
}
if (_socket->GetStatus() == Network::SocketStatus::listening)
catch (const std::exception& e)
{
duk_error(ctx, DUK_ERR_ERROR, "Server is already listening.");
JS_ThrowInternalError(ctx, "%s", e.what());
}
}
return JS_DupValue(ctx, thisVal);
}
static JSValue end(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid socket");
if (data->_disposed)
{
JS_ThrowPlainError(ctx, "Socket is disposed.");
}
else if (data->_socket != nullptr)
{
if (JS_IsString(argv[0]))
{
if (JSValue res = write(ctx, thisVal, argc, argv); JS_IsException(res))
{
return res;
}
data->_socket->Finish();
}
else
{
if (dukHost.type() == DukValue::Type::STRING)
{
auto host = dukHost.as_string();
if (IsLocalhostAddress(host) || IsOnWhiteList(host))
{
try
{
_socket->Listen(host, port);
}
catch (const std::exception& e)
{
duk_error(ctx, DUK_ERR_ERROR, e.what());
}
}
else
{
duk_error(ctx, DUK_ERR_ERROR, "For security reasons, only binding to localhost is allowed.");
}
}
else
{
_socket->Listen("127.0.0.1", port);
}
data->_socket->Finish();
JS_ThrowPlainError(ctx, "Only sending strings is currently supported.");
}
}
return this;
return JS_DupValue(ctx, thisVal);
}
ScListener* on(const std::string& eventType, const DukValue& callback)
static JSValue write(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto eventId = GetEventType(eventType);
if (eventId != EVENT_NONE)
JS_UNPACK_STR(str, ctx, argv[0]);
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid socket");
if (data->_disposed)
{
_eventList.AddListener(eventId, callback);
JS_ThrowPlainError(ctx, "Socket is disposed.");
}
return this;
}
ScListener* off(const std::string& eventType, const DukValue& callback)
{
auto eventId = GetEventType(eventType);
if (eventId != EVENT_NONE)
else if (data->_socket != nullptr)
{
_eventList.RemoveListener(eventId, callback);
try
{
auto sentBytes = data->_socket->SendData(str.c_str(), str.size());
return JS_NewBool(ctx, sentBytes != str.size());
}
catch (const std::exception&)
{
return JS_NewBool(ctx, false);
}
}
return this;
return JS_NewBool(ctx, false);
}
uint32_t GetEventType(std::string_view name)
static JSValue on(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
if (name == "connection")
return EVENT_CONNECTION;
return EVENT_NONE;
JS_UNPACK_STR(eventType, ctx, argv[0]);
JS_UNPACK_CALLBACK(callback, ctx, argv[1]);
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid socket");
auto eventId = SocketData::GetEventType(eventType);
if (eventId != SocketData::EVENT_NONE)
{
data->_eventList.AddListener(eventId, callback);
}
return JS_DupValue(ctx, thisVal);
}
static JSValue off(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_STR(eventType, ctx, argv[0]);
JS_UNPACK_CALLBACK(callback, ctx, argv[1]);
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid socket");
auto eventId = SocketData::GetEventType(eventType);
if (eventId != SocketData::EVENT_NONE)
{
data->_eventList.RemoveListener(eventId, callback);
}
return JS_DupValue(ctx, thisVal);
}
public:
ScListener(const std::shared_ptr<Plugin>& plugin)
: ScSocketBase(plugin)
static constexpr JSCFunctionListEntry funcs[] = {
JS_CFUNC_DEF("destroy", 1, ScSocket::destroy), JS_CFUNC_DEF("setNoDelay", 1, ScSocket::setNoDelay),
JS_CFUNC_DEF("connect", 3, ScSocket::connect), JS_CFUNC_DEF("end", 1, ScSocket::end),
JS_CFUNC_DEF("write", 1, ScSocket::write), JS_CFUNC_DEF("on", 2, ScSocket::on),
JS_CFUNC_DEF("off", 2, ScSocket::off),
};
JSValue New(JSContext* ctx, const std::shared_ptr<Plugin>& plugin)
{
// unique ptr is used to avoid static analyzer false positives.
auto data = std::make_unique<SocketData>();
data->_plugin = plugin;
GetContext()->GetScriptEngine().AddSocket(data.get());
return MakeWithOpaque(ctx, funcs, data.release());
}
JSValue New(JSContext* ctx, const std::shared_ptr<Plugin>& plugin, std::unique_ptr<Network::ITcpSocket>&& socket)
{
auto data = std::make_unique<SocketData>();
data->_plugin = plugin;
data->_socket = std::move(socket);
GetContext()->GetScriptEngine().AddSocket(data.get());
return MakeWithOpaque(ctx, funcs, data.release());
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Socket", Finalize);
}
static void Finalize(JSRuntime* rt, JSValue thisVal)
{
SocketData* data = gScSocket.GetOpaque<SocketData*>(thisVal);
if (data)
{
// Note the game context pointer can be null if the game is shutting down,
// but all sockets should have been cleaned up by then.
IContext* gameContext = GetContext();
assert(gameContext != nullptr);
gameContext->GetScriptEngine().RemoveSocket(data);
data->Dispose();
delete data;
}
}
};
struct ListenerData final : SocketDataBase
{
static constexpr uint32_t EVENT_NONE = std::numeric_limits<uint32_t>::max();
static constexpr uint32_t EVENT_CONNECTION = 0;
~ListenerData() override
{
ListenerData::Dispose();
}
void Update() override
@@ -514,13 +477,9 @@ namespace OpenRCT2::Scripting
// Default to using Nagle's algorithm like node.js does
client->SetNoDelay(false);
auto& scriptEngine = GetContext()->GetScriptEngine();
auto clientSocket = std::make_shared<ScSocket>(GetPlugin(), std::move(client));
scriptEngine.AddSocket(clientSocket);
auto ctx = scriptEngine.GetContext();
auto dukClientSocket = GetObjectAsDukValue(ctx, clientSocket);
_eventList.Raise(EVENT_CONNECTION, GetPlugin(), { dukClientSocket }, false);
JSContext* ctx = GetContext()->GetScriptEngine().GetContext();
auto clientSocket = gScSocket.New(ctx, _plugin, std::move(client));
_eventList.Raise(EVENT_CONNECTION, _plugin, { clientSocket }, false);
}
}
}
@@ -532,6 +491,7 @@ namespace OpenRCT2::Scripting
_socket->Close();
_socket = nullptr;
}
_eventList.RemoveAllListeners();
}
void Dispose() override
@@ -539,19 +499,172 @@ namespace OpenRCT2::Scripting
CloseSocket();
_disposed = true;
}
};
bool IsDisposed() const override
class ScListener;
extern ScListener gScListener;
class ScListener final : public ScBase
{
static ListenerData* GetData(JSValue thisVal)
{
return _disposed;
return gScListener.GetOpaque<ListenerData*>(thisVal);
}
static void Register(duk_context* ctx)
static JSValue listening_get(JSContext* ctx, JSValue thisVal)
{
dukglue_register_property(ctx, &ScListener::listening_get, nullptr, "listening");
dukglue_register_method(ctx, &ScListener::close, "close");
dukglue_register_method(ctx, &ScListener::listen, "listen");
dukglue_register_method(ctx, &ScListener::on, "on");
dukglue_register_method(ctx, &ScListener::off, "off");
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid listener");
if (data->_socket != nullptr)
{
return JS_NewBool(ctx, data->_socket->GetStatus() == Network::SocketStatus::listening);
}
return JS_NewBool(ctx, false);
}
static JSValue close(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid listener");
data->CloseSocket();
return JS_DupValue(ctx, thisVal);
}
static JSValue listen(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_UINT32(port, ctx, argv[0]);
if (port & 0xFFFF0000)
return JS_ThrowRangeError(ctx, "Invalid port.");
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid listener");
if (data->_disposed)
{
JS_ThrowPlainError(ctx, "Socket is disposed.");
}
else
{
if (data->_socket == nullptr)
{
data->_socket = Network::CreateTcpSocket();
}
if (data->_socket->GetStatus() == Network::SocketStatus::listening)
{
JS_ThrowPlainError(ctx, "Server is already listening.");
}
else
{
if (JS_IsString(argv[1]))
{
std::string host = JSToStdString(ctx, argv[1]);
if (IsLocalhostAddress(host) || IsOnWhiteList(host))
{
try
{
data->_socket->Listen(host, port);
}
catch (const std::exception& e)
{
JS_ThrowPlainError(ctx, "%s", e.what());
}
}
else
{
JS_ThrowPlainError(ctx, "For security reasons, only binding to localhost is allowed.");
}
}
else
{
data->_socket->Listen("127.0.0.1", port);
}
}
}
return JS_DupValue(ctx, thisVal);
}
static JSValue on(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_STR(eventType, ctx, argv[0]);
JS_UNPACK_CALLBACK(callback, ctx, argv[1]);
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid listener");
auto eventId = GetEventType(eventType);
if (eventId != ListenerData::EVENT_NONE)
{
data->_eventList.AddListener(eventId, callback);
}
return JS_DupValue(ctx, thisVal);
}
static JSValue off(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_STR(eventType, ctx, argv[0]);
JS_UNPACK_CALLBACK(callback, ctx, argv[1]);
auto data = GetData(thisVal);
if (!data)
return JS_ThrowInternalError(ctx, "Invalid listener");
auto eventId = GetEventType(eventType);
if (eventId != ListenerData::EVENT_NONE)
{
data->_eventList.RemoveListener(eventId, callback);
}
return JS_DupValue(ctx, thisVal);
}
static uint32_t GetEventType(std::string_view name)
{
if (name == "connection")
return ListenerData::EVENT_CONNECTION;
return ListenerData::EVENT_NONE;
}
public:
JSValue New(JSContext* ctx, const std::shared_ptr<Plugin>& plugin)
{
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("listening", ScListener::listening_get, nullptr),
JS_CFUNC_DEF("close", 0, ScListener::close),
JS_CFUNC_DEF("listen", 2, ScListener::listen),
JS_CFUNC_DEF("on", 2, ScListener::on),
JS_CFUNC_DEF("off", 2, ScListener::off),
};
// unique ptr is used to avoid static analyzer false positives.
auto data = std::make_unique<ListenerData>();
data->_plugin = plugin;
GetContext()->GetScriptEngine().AddSocket(data.get());
return MakeWithOpaque(ctx, funcs, data.release());
}
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "Listener", Finalize);
}
static void Finalize(JSRuntime* rt, JSValue thisVal)
{
ListenerData* data = gScListener.GetOpaque<ListenerData*>(thisVal);
if (data)
{
// Note the game context pointer can be null if the game is shutting down,
// but all sockets should have been cleaned up by then.
IContext* gameContext = GetContext();
assert(gameContext != nullptr);
gameContext->GetScriptEngine().RemoveSocket(data);
data->Dispose();
delete data;
}
}
};
} // namespace OpenRCT2::Scripting
@@ -9,6 +9,8 @@
#ifdef ENABLE_SCRIPTING
#include "../../../core/EnumMap.hpp"
#include "../../../object/ObjectTypes.h"
#include "ScObject.hpp"
namespace OpenRCT2::Scripting
@@ -13,7 +13,6 @@
#include "../../../Context.h"
#include "../../../object/ObjectRepository.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
#include <optional>
@@ -34,137 +33,166 @@ namespace OpenRCT2::Scripting
return values[EnumValue(sourceGame)];
}
class ScInstalledObject
class ScInstalledObject;
extern ScInstalledObject gScInstalledObject;
class ScInstalledObject final : public ScBase
{
protected:
size_t _index{};
private:
struct InstalledObjectData
{
size_t _index{};
};
public:
ScInstalledObject(size_t index)
: _index(index)
void Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "InstalledObject", Finalize);
}
static void Register(duk_context* ctx)
JSValue New(JSContext* ctx, size_t index)
{
dukglue_register_property(ctx, &ScInstalledObject::path_get, nullptr, "path");
dukglue_register_property(ctx, &ScInstalledObject::generation_get, nullptr, "generation");
dukglue_register_property(ctx, &ScInstalledObject::identifier_get, nullptr, "identifier");
dukglue_register_property(ctx, &ScInstalledObject::type_get, nullptr, "type");
dukglue_register_property(ctx, &ScInstalledObject::sourceGames_get, nullptr, "sourceGames");
dukglue_register_property(ctx, &ScInstalledObject::legacyIdentifier_get, nullptr, "legacyIdentifier");
dukglue_register_property(ctx, &ScInstalledObject::authors_get, nullptr, "authors");
dukglue_register_property(ctx, &ScInstalledObject::name_get, nullptr, "name");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("path", ScInstalledObject::path_get, nullptr),
JS_CGETSET_DEF("generation", ScInstalledObject::generation_get, nullptr),
JS_CGETSET_DEF("identifier", ScInstalledObject::identifier_get, nullptr),
JS_CGETSET_DEF("type", ScInstalledObject::type_get, nullptr),
JS_CGETSET_DEF("sourceGames", ScInstalledObject::sourceGames_get, nullptr),
JS_CGETSET_DEF("legacyIdentifier", ScInstalledObject::legacyIdentifier_get, nullptr),
JS_CGETSET_DEF("authors", ScInstalledObject::authors_get, nullptr),
JS_CGETSET_DEF("name", ScInstalledObject::name_get, nullptr),
};
return MakeWithOpaque(ctx, funcs, new InstalledObjectData{ index });
}
private:
std::string path_get() const
static void Finalize(JSRuntime* rt, JSValue thisVal)
{
auto installedObject = GetInstalledObject();
if (installedObject != nullptr)
{
return installedObject->Path;
}
return {};
InstalledObjectData* data = gScInstalledObject.GetOpaque<InstalledObjectData*>(thisVal);
if (data)
delete data;
}
std::string generation_get() const
static JSValue path_get(JSContext* ctx, JSValue thisVal)
{
auto installedObject = GetInstalledObject();
auto installedObject = GetInstalledObject(thisVal);
if (installedObject != nullptr)
{
return JSFromStdString(ctx, installedObject->Path);
}
return JSFromStdString(ctx, "");
}
static JSValue generation_get(JSContext* ctx, JSValue thisVal)
{
auto installedObject = GetInstalledObject(thisVal);
if (installedObject != nullptr)
{
if (installedObject->Generation == ObjectGeneration::DAT)
return "dat";
return JSFromStdString(ctx, "dat");
else
return "json";
return JSFromStdString(ctx, "json");
}
return {};
return JSFromStdString(ctx, "");
}
std::vector<std::string> sourceGames_get() const
static JSValue sourceGames_get(JSContext* ctx, JSValue thisVal)
{
std::vector<std::string> result;
auto installedObject = GetInstalledObject();
auto installedObject = GetInstalledObject(thisVal);
JSValue array = JS_NewArray(ctx);
if (installedObject != nullptr)
{
int64_t index = 0;
for (const auto& sourceGame : installedObject->Sources)
{
result.push_back(std::string(ObjectSourceGameToString(sourceGame)));
JSValue sourceGameStr = JSFromStdString(ctx, std::string(ObjectSourceGameToString(sourceGame)));
JS_SetPropertyInt64(ctx, array, index++, sourceGameStr);
}
}
return result;
return array;
}
std::string type_get() const
static JSValue type_get(JSContext* ctx, JSValue thisVal)
{
auto installedObject = GetInstalledObject();
auto installedObject = GetInstalledObject(thisVal);
if (installedObject != nullptr)
{
return std::string(objectTypeToString(installedObject->Type));
return JSFromStdString(ctx, std::string(objectTypeToString(installedObject->Type)));
}
return {};
return JSFromStdString(ctx, "");
}
std::string identifier_get() const
static JSValue identifier_get(JSContext* ctx, JSValue thisVal)
{
auto installedObject = GetInstalledObject();
auto installedObject = GetInstalledObject(thisVal);
if (installedObject != nullptr)
{
if (installedObject->Generation == ObjectGeneration::DAT)
{
return ObjectEntryDescriptor(installedObject->ObjectEntry).ToString();
return JSFromStdString(ctx, ObjectEntryDescriptor(installedObject->ObjectEntry).ToString());
}
else
{
return installedObject->Identifier;
return JSFromStdString(ctx, installedObject->Identifier);
}
}
return {};
return JSFromStdString(ctx, "");
}
DukValue legacyIdentifier_get() const
static JSValue legacyIdentifier_get(JSContext* ctx, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto installedObject = GetInstalledObject();
auto installedObject = GetInstalledObject(thisVal);
if (installedObject != nullptr)
{
if (!installedObject->ObjectEntry.IsEmpty())
{
return ToDuk(ctx, installedObject->ObjectEntry.GetName());
auto str = installedObject->ObjectEntry.GetName();
if (str.find('\0') != std::string::npos)
str = {};
return JSFromStdString(ctx, str);
}
}
return ToDuk(ctx, nullptr);
return JS_NULL;
}
std::vector<std::string> authors_get() const
static JSValue authors_get(JSContext* ctx, JSValue thisVal)
{
auto installedObject = GetInstalledObject();
auto installedObject = GetInstalledObject(thisVal);
JSValue array = JS_NewArray(ctx);
if (installedObject != nullptr)
{
return installedObject->Authors;
uint32_t index = 0;
for (const auto& author : installedObject->Authors)
{
JSValue authorStr = JSFromStdString(ctx, author);
JS_SetPropertyInt64(ctx, array, index++, authorStr);
}
}
return {};
return array;
}
std::string name_get() const
static JSValue name_get(JSContext* ctx, JSValue thisVal)
{
auto installedObject = GetInstalledObject();
auto installedObject = GetInstalledObject(thisVal);
if (installedObject != nullptr)
{
return installedObject->Name;
return JSFromStdString(ctx, installedObject->Name);
}
return {};
return JSFromStdString(ctx, "");
}
const ObjectRepositoryItem* GetInstalledObject() const
static const ObjectRepositoryItem* GetInstalledObject(JSValue thisVal)
{
size_t index = gScInstalledObject.GetOpaque<InstalledObjectData*>(thisVal)->_index;
auto context = GetContext();
auto& objectRepository = context->GetObjectRepository();
auto numObjects = objectRepository.GetNumObjects();
if (_index < numObjects)
if (index < numObjects)
{
auto* objects = objectRepository.GetObjects();
return &objects[_index];
return &objects[index];
}
return nullptr;
}
File diff suppressed because it is too large Load Diff
@@ -15,69 +15,78 @@
#include "../../../object/ObjectList.h"
#include "../../../ride/RideData.h"
#include "../../../windows/Intent.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
using namespace OpenRCT2;
using namespace OpenRCT2::Scripting;
void ScObjectManager::Register(duk_context* ctx)
void ScObjectManager::Register(JSContext* ctx)
{
dukglue_register_property(ctx, &ScObjectManager::installedObjects_get, nullptr, "installedObjects");
dukglue_register_method(ctx, &ScObjectManager::installedObject_get, "getInstalledObject");
dukglue_register_method(ctx, &ScObjectManager::load, "load");
dukglue_register_method(ctx, &ScObjectManager::unload, "unload");
dukglue_register_method(ctx, &ScObjectManager::getObject, "getObject");
dukglue_register_method(ctx, &ScObjectManager::getAllObjects, "getAllObjects");
RegisterBaseStr(ctx, "ObjectManager");
}
std::vector<std::shared_ptr<ScInstalledObject>> ScObjectManager::installedObjects_get() const
JSValue ScObjectManager::New(JSContext* ctx)
{
std::vector<std::shared_ptr<ScInstalledObject>> result;
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("installedObjects", ScObjectManager::installedObjects_get, nullptr),
JS_CFUNC_DEF("getInstalledObject", 1, ScObjectManager::getInstalledObject),
JS_CFUNC_DEF("load", 2, ScObjectManager::load),
JS_CFUNC_DEF("unload", 1, ScObjectManager::unload),
JS_CFUNC_DEF("getObject", 2, ScObjectManager::getObject),
JS_CFUNC_DEF("getAllObjects", 1, ScObjectManager::getAllObjects),
};
return MakeWithOpaque(ctx, funcs, nullptr);
}
JSValue ScObjectManager::installedObjects_get(JSContext* ctx, JSValue thisVal)
{
JSValue result = JS_NewArray(ctx);
auto context = GetContext();
auto& objectManager = context->GetObjectRepository();
auto count = objectManager.GetNumObjects();
for (size_t i = 0; i < count; i++)
auto count = static_cast<int64_t>(objectManager.GetNumObjects());
for (int64_t i = 0; i < count; i++)
{
auto installedObject = std::make_shared<ScInstalledObject>(i);
result.push_back(installedObject);
JSValue installedObject = gScInstalledObject.New(ctx, i);
JS_SetPropertyInt64(ctx, result, i, installedObject);
}
return result;
}
std::shared_ptr<ScInstalledObject> ScObjectManager::installedObject_get(const std::string& identifier) const
JSValue ScObjectManager::getInstalledObject(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_STR(identifier, ctx, argv[0]);
auto context = GetContext();
auto& objectRepository = context->GetObjectRepository();
auto object = objectRepository.FindObject(identifier);
return object != nullptr ? std::make_shared<ScInstalledObject>(object->Id) : nullptr;
return object != nullptr ? gScInstalledObject.New(ctx, object->Id) : JS_NULL;
}
DukValue ScObjectManager::load(const DukValue& p1, const DukValue& p2)
JSValue ScObjectManager::load(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto context = GetContext();
auto& scriptEngine = context->GetScriptEngine();
auto& objectRepository = context->GetObjectRepository();
auto& objectManager = context->GetObjectManager();
auto ctx = scriptEngine.GetContext();
if (p1.is_array())
if (JS_IsArray(argv[0]))
{
// load(identifiers)
JS_UNPACK_ARRAY(array, ctx, argv[0]);
std::vector<ObjectEntryDescriptor> descriptors;
for (const auto& item : p1.as_array())
{
if (item.type() != DukValue::STRING)
throw DukException() << "Expected string for 'identifier'.";
int64_t length;
JS_GetLength(ctx, array, &length);
const auto& identifier = item.as_string();
for (int64_t i = 0; i < length; i++)
{
JSValue item = JS_GetPropertyInt64(ctx, array, i);
JS_UNPACK_STR(identifier, ctx, item);
descriptors.push_back(ObjectEntryDescriptor::Parse(identifier));
}
duk_push_array(ctx);
duk_uarridx_t index = 0;
JSValue result = JS_NewArray(ctx);
int64_t index = 0;
for (const auto& descriptor : descriptors)
{
auto obj = objectManager.LoadObject(descriptor);
@@ -85,38 +94,31 @@ DukValue ScObjectManager::load(const DukValue& p1, const DukValue& p2)
{
MarkAsResearched(obj);
auto objIndex = objectManager.GetLoadedObjectEntryIndex(obj);
auto scLoadedObject = CreateScObject(scriptEngine.GetContext(), obj->GetObjectType(), objIndex);
scLoadedObject.push();
duk_put_prop_index(ctx, -2, index);
auto scLoadedObject = CreateScObject(ctx, obj->GetObjectType(), objIndex);
JS_SetPropertyInt64(ctx, result, index, scLoadedObject);
}
else
{
duk_push_null(ctx);
duk_put_prop_index(ctx, -2, index);
JS_SetPropertyInt64(ctx, result, index, JS_NULL);
}
index++;
}
RefreshResearchedItems();
return DukValue::take_from_stack(ctx);
return result;
}
else
{
// load(identifier, index?)
if (p1.type() != DukValue::STRING)
throw DukException() << "Expected string for 'identifier'.";
const auto& identifier = p1.as_string();
JS_UNPACK_STR(identifier, ctx, argv[0]);
auto descriptor = ObjectEntryDescriptor::Parse(identifier);
auto installedObject = objectRepository.FindObject(descriptor);
if (installedObject != nullptr)
{
if (p2.type() != DukValue::UNDEFINED)
if (argc > 1 && !JS_IsUndefined(argv[1]))
{
if (p2.type() != DukValue::NUMBER)
throw DukException() << "Expected number for 'index'.";
JS_UNPACK_UINT32(index, ctx, argv[1]);
auto index = static_cast<size_t>(p2.as_uint());
auto limit = getObjectTypeLimit(installedObject->Type);
if (index < limit)
{
@@ -131,7 +133,7 @@ DukValue ScObjectManager::load(const DukValue& p1, const DukValue& p2)
MarkAsResearched(obj);
RefreshResearchedItems();
auto objIndex = objectManager.GetLoadedObjectEntryIndex(obj);
return CreateScObject(scriptEngine.GetContext(), obj->GetObjectType(), objIndex);
return CreateScObject(ctx, obj->GetObjectType(), objIndex);
}
}
}
@@ -143,30 +145,28 @@ DukValue ScObjectManager::load(const DukValue& p1, const DukValue& p2)
MarkAsResearched(obj);
RefreshResearchedItems();
auto objIndex = objectManager.GetLoadedObjectEntryIndex(obj);
return CreateScObject(scriptEngine.GetContext(), obj->GetObjectType(), objIndex);
return CreateScObject(ctx, obj->GetObjectType(), objIndex);
}
}
}
}
return ToDuk(ctx, nullptr);
return JS_NULL;
}
void ScObjectManager::unload(const DukValue& p1, const DukValue& p2)
JSValue ScObjectManager::unload(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
// TODO: shouldn't this throw JS_THROW_IF_GAME_STATE_NOT_MUTABLE()?
auto context = GetContext();
auto& objectManager = context->GetObjectManager();
if (p1.type() == DukValue::STRING)
if (JS_IsString(argv[0]))
{
const auto& szP1 = p1.as_string();
JS_UNPACK_STR(szP1, ctx, argv[0]);
auto objType = objectTypeFromString(szP1);
if (objType != ObjectType::none)
{
// unload(type, index)
if (p2.type() != DukValue::NUMBER)
throw DukException() << "'index' is invalid.";
auto objIndex = p2.as_uint();
JS_UNPACK_UINT32(objIndex, ctx, argv[1]);
auto obj = objectManager.GetLoadedObject(objType, objIndex);
if (obj != nullptr)
{
@@ -179,27 +179,35 @@ void ScObjectManager::unload(const DukValue& p1, const DukValue& p2)
objectManager.UnloadObjects({ ObjectEntryDescriptor::Parse(szP1) });
}
}
else if (p1.is_array())
else if (JS_IsArray(argv[0]))
{
// unload(identifiers)
auto identifiers = p1.as_array();
JS_UNPACK_ARRAY(array, ctx, argv[0]);
int64_t length;
JS_GetLength(ctx, array, &length);
std::vector<ObjectEntryDescriptor> descriptors;
for (const auto& identifier : identifiers)
for (int64_t i = 0; i < length; i++)
{
if (identifier.type() == DukValue::STRING)
JSValue item = JS_GetPropertyInt64(ctx, array, i);
if (JS_IsString(item))
{
descriptors.push_back(ObjectEntryDescriptor::Parse(identifier.as_string()));
JS_UNPACK_STR(identifier, ctx, item);
descriptors.push_back(ObjectEntryDescriptor::Parse(identifier));
}
}
objectManager.UnloadObjects(descriptors);
}
auto intent = Intent(INTENT_ACTION_REFRESH_SCENERY);
ContextBroadcastIntent(&intent);
return JS_UNDEFINED;
}
DukValue ScObjectManager::getObject(const std::string& typez, int32_t index) const
JSValue ScObjectManager::getObject(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
JS_UNPACK_STR(typez, ctx, argv[0]);
JS_UNPACK_INT32(index, ctx, argv[1]);
auto& objManager = GetContext()->GetObjectManager();
auto type = objectTypeFromString(typez);
@@ -213,35 +221,35 @@ DukValue ScObjectManager::getObject(const std::string& typez, int32_t index) con
}
else
{
duk_error(ctx, DUK_ERR_ERROR, "Invalid object type.");
return JS_ThrowPlainError(ctx, "Invalid object type.");
}
return ToDuk(ctx, nullptr);
return JS_NULL;
}
std::vector<DukValue> ScObjectManager::getAllObjects(const std::string& typez) const
JSValue ScObjectManager::getAllObjects(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto& objManager = GetContext()->GetObjectManager();
JS_UNPACK_STR(typez, ctx, argv[0]);
std::vector<DukValue> result;
auto& objManager = GetContext()->GetObjectManager();
auto type = objectTypeFromString(typez);
if (type != ObjectType::none)
{
auto count = getObjectEntryGroupCount(type);
for (auto i = 0u; i < count; i++)
JSValue result = JS_NewArray(ctx);
size_t count = getObjectEntryGroupCount(type);
int64_t resultIndex = 0;
for (size_t i = 0; i < count; i++)
{
auto obj = objManager.GetLoadedObject(type, i);
if (obj != nullptr)
{
result.push_back(CreateScObject(ctx, type, i));
JSValue scObj = CreateScObject(ctx, type, static_cast<int32_t>(i));
JS_SetPropertyInt64(ctx, result, resultIndex++, scObj);
}
}
return result;
}
else
{
duk_error(ctx, DUK_ERR_ERROR, "Invalid object type.");
}
return result;
return JS_ThrowPlainError(ctx, "Invalid object type.");
}
void ScObjectManager::MarkAsResearched(const Object* object)
@@ -270,26 +278,42 @@ void ScObjectManager::RefreshResearchedItems()
gSilentResearch = false;
}
DukValue ScObjectManager::CreateScObject(duk_context* ctx, ObjectType type, int32_t index)
JSValue ScObjectManager::CreateScObject(JSContext* ctx, ObjectType type, int32_t index)
{
switch (type)
{
case ObjectType::ride:
return GetObjectAsDukValue(ctx, std::make_shared<ScRideObject>(type, index));
{
return ScRideObject::New(ctx, type, index);
}
case ObjectType::smallScenery:
return GetObjectAsDukValue(ctx, std::make_shared<ScSmallSceneryObject>(type, index));
{
return ScSmallSceneryObject::New(ctx, type, index);
}
case ObjectType::largeScenery:
return GetObjectAsDukValue(ctx, std::make_shared<ScLargeSceneryObject>(type, index));
{
return ScLargeSceneryObject::New(ctx, type, index);
}
case ObjectType::walls:
return GetObjectAsDukValue(ctx, std::make_shared<ScWallObject>(type, index));
{
return ScWallObject::New(ctx, type, index);
}
case ObjectType::pathAdditions:
return GetObjectAsDukValue(ctx, std::make_shared<ScFootpathAdditionObject>(type, index));
{
return ScFootpathAdditionObject::New(ctx, type, index);
}
case ObjectType::banners:
return GetObjectAsDukValue(ctx, std::make_shared<ScBannerObject>(type, index));
{
return ScBannerObject::New(ctx, type, index);
}
case ObjectType::sceneryGroup:
return GetObjectAsDukValue(ctx, std::make_shared<ScSceneryGroupObject>(type, index));
{
return ScSceneryGroupObject::New(ctx, type, index);
}
default:
return GetObjectAsDukValue(ctx, std::make_shared<ScObject>(type, index));
{
return ScObject::New(ctx, type, index);
}
}
}
@@ -11,7 +11,6 @@
#ifdef ENABLE_SCRIPTING
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
#include "ScInstalledObject.hpp"
#include "ScObject.hpp"
@@ -20,24 +19,28 @@
namespace OpenRCT2::Scripting
{
class ScObjectManager
class ScObjectManager;
extern ScObjectManager gScObjectManager;
class ScObjectManager final : public ScBase
{
public:
static void Register(duk_context* ctx);
void Register(JSContext* ctx);
JSValue New(JSContext* ctx);
std::vector<std::shared_ptr<ScInstalledObject>> installedObjects_get() const;
std::shared_ptr<ScInstalledObject> installedObject_get(const std::string& identifier) const;
DukValue load(const DukValue& p1, const DukValue& p2);
void unload(const DukValue& p1, const DukValue& p2);
DukValue getObject(const std::string& typez, int32_t index) const;
std::vector<DukValue> getAllObjects(const std::string& typez) const;
static JSValue getObject(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue getAllObjects(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
private:
static JSValue installedObjects_get(JSContext* ctx, JSValue thisVal);
static JSValue getInstalledObject(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue load(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue unload(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static void MarkAsResearched(const Object* object);
static void RefreshResearchedItems();
static DukValue CreateScObject(duk_context* ctx, ObjectType type, int32_t index);
static JSValue CreateScObject(JSContext* ctx, ObjectType type, int32_t index);
};
} // namespace OpenRCT2::Scripting
File diff suppressed because it is too large Load Diff
+119 -172
View File
@@ -9,197 +9,144 @@
#pragma once
#ifdef ENABLE_SCRIPTING
#include "../../../Context.h"
#include "../../../ride/Ride.h"
#include "../../ScriptEngine.h"
#include "../object/ScObject.hpp"
#include "ScRideStation.hpp"
#include "../../../Context.h"
#include "../../../ride/Ride.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
#include "../object/ScObject.hpp"
#include "ScRideStation.hpp"
#ifdef ENABLE_SCRIPTING
namespace OpenRCT2::Scripting
{
template<>
inline DukValue ToDuk(duk_context* ctx, const TrackColour& value)
inline JSValue ToJSValue(JSContext* ctx, const VehicleColour& value)
{
DukObject obj(ctx);
obj.Set("main", EnumValue(value.main));
obj.Set("additional", EnumValue(value.additional));
obj.Set("supports", EnumValue(value.supports));
return obj.Take();
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "body", JS_NewInt32(ctx, EnumValue(value.Body)));
JS_SetPropertyStr(ctx, obj, "trim", JS_NewInt32(ctx, EnumValue(value.Trim)));
JS_SetPropertyStr(ctx, obj, "ternary", JS_NewInt32(ctx, EnumValue(value.Tertiary)));
JS_SetPropertyStr(ctx, obj, "tertiary", JS_NewInt32(ctx, EnumValue(value.Tertiary)));
return obj;
}
template<>
inline TrackColour FromDuk(const DukValue& s)
{
TrackColour result{};
result.main = static_cast<Drawing::Colour>(AsOrDefault(s["main"], 0));
result.additional = static_cast<Drawing::Colour>(AsOrDefault(s["additional"], 0));
result.supports = static_cast<Drawing::Colour>(AsOrDefault(s["supports"], 0));
return result;
}
template<>
inline DukValue ToDuk(duk_context* ctx, const VehicleColour& value)
{
DukObject obj(ctx);
obj.Set("body", EnumValue(value.Body));
obj.Set("trim", EnumValue(value.Trim));
obj.Set("ternary", EnumValue(value.Tertiary));
obj.Set("tertiary", EnumValue(value.Tertiary));
return obj.Take();
}
template<>
inline VehicleColour FromDuk(const DukValue& s)
inline VehicleColour JSToVehicleColours(JSContext* ctx, JSValue val)
{
VehicleColour result{};
result.Body = static_cast<Drawing::Colour>(AsOrDefault(s["body"], 0));
result.Trim = static_cast<Drawing::Colour>(AsOrDefault(s["trim"], 0));
result.Tertiary = static_cast<Drawing::Colour>(AsOrDefault(s["ternary"], 0));
result.Tertiary = static_cast<Drawing::Colour>(AsOrDefault<int32_t>(s["tertiary"], EnumValue(result.Tertiary)));
result.Body = static_cast<Drawing::Colour>(AsOrDefault(ctx, val, "body", 0));
result.Trim = static_cast<Drawing::Colour>(AsOrDefault(ctx, val, "trim", 0));
result.Tertiary = static_cast<Drawing::Colour>(AsOrDefault(ctx, val, "ternary", 0));
result.Tertiary = static_cast<Drawing::Colour>(
AsOrDefault(ctx, val, "tertiary", static_cast<uint32_t>(result.Tertiary)));
return result;
}
class ScRide
inline JSValue ToJSValue(JSContext* ctx, const TrackColour& value)
{
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "main", JS_NewInt32(ctx, EnumValue(value.main)));
JS_SetPropertyStr(ctx, obj, "additional", JS_NewInt32(ctx, EnumValue(value.additional)));
JS_SetPropertyStr(ctx, obj, "supports", JS_NewInt32(ctx, EnumValue(value.supports)));
return obj;
}
inline TrackColour JSToTrackColour(JSContext* ctx, JSValue val)
{
TrackColour result{};
result.main = static_cast<Drawing::Colour>(AsOrDefault(ctx, val, "main", 0));
result.additional = static_cast<Drawing::Colour>(AsOrDefault(ctx, val, "additional", 0));
result.supports = static_cast<Drawing::Colour>(AsOrDefault(ctx, val, "supports", 0));
return result;
}
class ScRide;
extern ScRide gScRide;
class ScRide final : public ScBase
{
private:
RideId _rideId = RideId::GetNull();
struct RideData
{
RideId _rideId = RideId::GetNull();
};
public:
ScRide(RideId rideId);
void Register(JSContext* ctx);
JSValue New(JSContext* ctx, RideId rideId);
private:
int32_t id_get() const;
static void Finalize(JSRuntime* rt, JSValue thisVal);
static RideData* GetRideData(JSValue thisVal);
static Ride* GetRide(JSValue thisVal);
std::shared_ptr<ScRideObject> object_get();
int32_t type_get() const;
std::string classification_get() const;
std::string name_get() const;
void name_set(std::string value);
std::string status_get() const;
uint32_t flags_get() const;
void flags_set(uint32_t value);
uint8_t mode_get() const;
void mode_set(uint8_t value);
uint8_t departFlags_get() const;
void departFlags_set(uint8_t value);
uint8_t minimumWaitingTime_get() const;
void minimumWaitingTime_set(uint8_t value);
uint8_t maximumWaitingTime_get() const;
void maximumWaitingTime_set(uint8_t value);
std::vector<uint16_t> vehicles_get() const;
std::vector<DukValue> vehicleColours_get() const;
void vehicleColours_set(const std::vector<DukValue>& value);
std::vector<DukValue> colourSchemes_get() const;
void colourSchemes_set(const std::vector<DukValue>& value);
ObjectEntryIndex stationStyle_get() const;
void stationStyle_set(ObjectEntryIndex value);
ObjectEntryIndex music_get() const;
void music_set(ObjectEntryIndex value);
std::vector<std::shared_ptr<ScRideStation>> stations_get() const;
std::vector<int32_t> price_get() const;
void price_set(const std::vector<int32_t>& value);
int32_t excitement_get() const;
void excitement_set(int32_t value);
int32_t intensity_get() const;
void intensity_set(int32_t value);
int32_t nausea_get() const;
void nausea_set(int32_t value);
int32_t totalCustomers_get() const;
void totalCustomers_set(int32_t value);
int32_t buildDate_get() const;
void buildDate_set(int32_t value);
int32_t age_get() const;
money64 runningCost_get() const;
void runningCost_set(money64 value);
int32_t totalProfit_get() const;
void totalProfit_set(int32_t value);
uint8_t inspectionInterval_get() const;
void inspectionInterval_set(uint8_t value);
DukValue value_get() const;
void value_set(const DukValue& value);
uint8_t downtime_get() const;
uint8_t liftHillSpeed_get() const;
void lifthillSpeed_set(uint8_t value);
uint8_t maxLiftHillSpeed_get() const;
uint8_t minLiftHillSpeed_get() const;
uint8_t satisfaction_get() const;
double maxSpeed_get() const;
double averageSpeed_get() const;
int32_t rideTime_get() const;
double rideLength_get() const;
double maxPositiveVerticalGs_get() const;
double maxNegativeVerticalGs_get() const;
double maxLateralGs_get() const;
double totalAirTime_get() const;
uint8_t numDrops_get() const;
uint8_t numLiftHills_get() const;
double highestDropHeight_get() const;
Ride* GetRide() const;
void SetBreakdown(const std::string& breakDown);
void FixBreakdown();
std::string getBreakdown() const;
public:
static void Register(duk_context* ctx);
static JSValue id_get(JSContext* ctx, JSValue thisVal);
static JSValue object_get(JSContext* ctx, JSValue thisVal);
static JSValue type_get(JSContext* ctx, JSValue thisVal);
static JSValue classification_get(JSContext* ctx, JSValue thisVal);
static JSValue name_get(JSContext* ctx, JSValue thisVal);
static JSValue name_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue status_get(JSContext* ctx, JSValue thisVal);
static JSValue flags_get(JSContext* ctx, JSValue thisVal);
static JSValue flags_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue mode_get(JSContext* ctx, JSValue thisVal);
static JSValue mode_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue departFlags_get(JSContext* ctx, JSValue thisVal);
static JSValue departFlags_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue minimumWaitingTime_get(JSContext* ctx, JSValue thisVal);
static JSValue minimumWaitingTime_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue maximumWaitingTime_get(JSContext* ctx, JSValue thisVal);
static JSValue maximumWaitingTime_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue vehicles_get(JSContext* ctx, JSValue thisVal);
static JSValue vehicleColours_get(JSContext* ctx, JSValue thisVal);
static JSValue vehicleColours_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue colourSchemes_get(JSContext* ctx, JSValue thisVal);
static JSValue colourSchemes_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue stationStyle_get(JSContext* ctx, JSValue thisVal);
static JSValue stationStyle_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue music_get(JSContext* ctx, JSValue thisVal);
static JSValue music_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue stations_get(JSContext* ctx, JSValue thisVal);
static JSValue price_get(JSContext* ctx, JSValue thisVal);
static JSValue price_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue excitement_get(JSContext* ctx, JSValue thisVal);
static JSValue excitement_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue intensity_get(JSContext* ctx, JSValue thisVal);
static JSValue intensity_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue nausea_get(JSContext* ctx, JSValue thisVal);
static JSValue nausea_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue totalCustomers_get(JSContext* ctx, JSValue thisVal);
static JSValue totalCustomers_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue buildDate_get(JSContext* ctx, JSValue thisVal);
static JSValue buildDate_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue age_get(JSContext* ctx, JSValue thisVal);
static JSValue runningCost_get(JSContext* ctx, JSValue thisVal);
static JSValue runningCost_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue totalProfit_get(JSContext* ctx, JSValue thisVal);
static JSValue totalProfit_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue inspectionInterval_get(JSContext* ctx, JSValue thisVal);
static JSValue inspectionInterval_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue value_get(JSContext* ctx, JSValue thisVal);
static JSValue value_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue downtime_get(JSContext* ctx, JSValue thisVal);
static JSValue liftHillSpeed_get(JSContext* ctx, JSValue thisVal);
static JSValue liftHillSpeed_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue maxLiftHillSpeed_get(JSContext* ctx, JSValue thisVal);
static JSValue minLiftHillSpeed_get(JSContext* ctx, JSValue thisVal);
static JSValue satisfaction_get(JSContext* ctx, JSValue thisVal);
static JSValue maxSpeed_get(JSContext* ctx, JSValue thisVal);
static JSValue averageSpeed_get(JSContext* ctx, JSValue thisVal);
static JSValue rideTime_get(JSContext* ctx, JSValue thisVal);
static JSValue rideLength_get(JSContext* ctx, JSValue thisVal);
static JSValue maxPositiveVerticalGs_get(JSContext* ctx, JSValue thisVal);
static JSValue maxNegativeVerticalGs_get(JSContext* ctx, JSValue thisVal);
static JSValue maxLateralGs_get(JSContext* ctx, JSValue thisVal);
static JSValue totalAirTime_get(JSContext* ctx, JSValue thisVal);
static JSValue numDrops_get(JSContext* ctx, JSValue thisVal);
static JSValue numLiftHills_get(JSContext* ctx, JSValue thisVal);
static JSValue highestDropHeight_get(JSContext* ctx, JSValue thisVal);
static JSValue breakdown_get(JSContext* ctx, JSValue thisVal);
static JSValue setBreakdown(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue fixBreakdown(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
};
} // namespace OpenRCT2::Scripting
@@ -13,121 +13,140 @@
#include "../../../Context.h"
#include "../../../ride/Ride.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
#include "../object/ScObject.hpp"
namespace OpenRCT2::Scripting
{
ScRideStation::ScRideStation(RideId rideId, StationIndex stationIndex)
: _rideId(rideId)
, _stationIndex(stationIndex)
void ScRideStation::Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "RideStation", Finalize);
}
void ScRideStation::Register(duk_context* ctx)
JSValue ScRideStation::New(JSContext* ctx, RideId rideId, StationIndex stationIndex)
{
dukglue_register_property(ctx, &ScRideStation::start_get, &ScRideStation::start_set, "start");
dukglue_register_property(ctx, &ScRideStation::length_get, &ScRideStation::length_set, "length");
dukglue_register_property(ctx, &ScRideStation::entrance_get, &ScRideStation::entrance_set, "entrance");
dukglue_register_property(ctx, &ScRideStation::exit_get, &ScRideStation::exit_set, "exit");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("start", ScRideStation::start_get, ScRideStation::start_set),
JS_CGETSET_DEF("length", ScRideStation::length_get, ScRideStation::length_set),
JS_CGETSET_DEF("entrance", ScRideStation::entrance_get, ScRideStation::entrance_set),
JS_CGETSET_DEF("exit", ScRideStation::exit_get, ScRideStation::exit_set),
};
return MakeWithOpaque(ctx, funcs, new RideStationData{ rideId, stationIndex });
}
DukValue ScRideStation::start_get() const
void ScRideStation::Finalize(JSRuntime* rt, JSValue thisVal)
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto station = GetRideStation();
if (station != nullptr)
{
auto start = CoordsXYZ(station->Start, station->GetBaseZ());
return ToDuk(ctx, start);
}
return ToDuk(ctx, nullptr);
RideStationData* data = GetRideStationData(thisVal);
if (data)
delete data;
}
void ScRideStation::start_set(const DukValue& value)
ScRideStation::RideStationData* ScRideStation::GetRideStationData(JSValue thisVal)
{
auto station = GetRideStation();
if (station != nullptr)
{
auto start = FromDuk<CoordsXYZ>(value);
station->Start = { start.x, start.y };
station->SetBaseZ(start.z);
}
return gScRideStation.GetOpaque<RideStationData*>(thisVal);
}
int32_t ScRideStation::length_get() const
RideStation* ScRideStation::GetRideStation(JSValue thisVal)
{
auto station = GetRideStation();
if (station != nullptr)
{
return station->Length;
}
return 0;
}
void ScRideStation::length_set(int32_t value)
{
auto station = GetRideStation();
if (station != nullptr)
{
station->Length = value;
}
}
DukValue ScRideStation::entrance_get() const
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto station = GetRideStation();
if (station != nullptr)
{
return ToDuk(ctx, station->Entrance.ToCoordsXYZD());
}
return ToDuk(ctx, nullptr);
}
void ScRideStation::entrance_set(const DukValue& value)
{
auto station = GetRideStation();
if (station != nullptr)
{
station->Entrance = FromDuk<CoordsXYZD>(value);
}
}
DukValue ScRideStation::exit_get() const
{
auto ctx = GetContext()->GetScriptEngine().GetContext();
auto station = GetRideStation();
if (station != nullptr)
{
return ToDuk(ctx, station->Exit.ToCoordsXYZD());
}
return ToDuk(ctx, nullptr);
}
void ScRideStation::exit_set(const DukValue& value)
{
auto station = GetRideStation();
if (station != nullptr)
{
station->Exit = FromDuk<CoordsXYZD>(value);
}
}
RideStation* ScRideStation::GetRideStation() const
{
auto ride = GetRide(_rideId);
RideStationData* data = GetRideStationData(thisVal);
auto ride = ::GetRide(data->_rideId);
if (ride != nullptr)
{
if (_stationIndex.ToUnderlying() < std::size(ride->getStations()))
if (data->_stationIndex.ToUnderlying() < std::size(ride->getStations()))
{
return &ride->getStation(_stationIndex);
return &ride->getStation(data->_stationIndex);
}
}
return nullptr;
}
JSValue ScRideStation::start_get(JSContext* ctx, JSValue thisVal)
{
auto station = GetRideStation(thisVal);
if (station != nullptr)
{
auto start = CoordsXYZ(station->Start, station->GetBaseZ());
return ToJSValue(ctx, start);
}
return JS_NULL;
}
JSValue ScRideStation::start_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto station = GetRideStation(thisVal);
if (station != nullptr)
{
auto start = JSToCoordsXYZ(ctx, value);
station->Start = { start.x, start.y };
station->SetBaseZ(start.z);
}
return JS_UNDEFINED;
}
JSValue ScRideStation::length_get(JSContext* ctx, JSValue thisVal)
{
auto station = GetRideStation(thisVal);
return JS_NewInt32(ctx, station != nullptr ? station->Length : 0);
}
JSValue ScRideStation::length_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_UNPACK_INT32(valueInt, ctx, value);
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto station = GetRideStation(thisVal);
if (station != nullptr)
{
station->Length = valueInt;
}
return JS_UNDEFINED;
}
JSValue ScRideStation::entrance_get(JSContext* ctx, JSValue thisVal)
{
auto station = GetRideStation(thisVal);
if (station != nullptr)
{
return ToJSValue(ctx, station->Entrance.ToCoordsXYZD());
}
return JS_NULL;
}
JSValue ScRideStation::entrance_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto station = GetRideStation(thisVal);
if (station != nullptr)
{
station->Entrance = JSToCoordsXYZD(ctx, value);
}
return JS_UNDEFINED;
}
JSValue ScRideStation::exit_get(JSContext* ctx, JSValue thisVal)
{
auto station = GetRideStation(thisVal);
if (station != nullptr)
{
return ToJSValue(ctx, station->Exit.ToCoordsXYZD());
}
return JS_NULL;
}
JSValue ScRideStation::exit_set(JSContext* ctx, JSValue thisVal, JSValue value)
{
JS_THROW_IF_GAME_STATE_NOT_MUTABLE();
auto station = GetRideStation(thisVal);
if (station != nullptr)
{
station->Exit = JSToCoordsXYZD(ctx, value);
}
return JS_UNDEFINED;
}
} // namespace OpenRCT2::Scripting
#endif
@@ -13,39 +13,39 @@
#include "../../../Context.h"
#include "../../../ride/Ride.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
namespace OpenRCT2::Scripting
{
class ScRideStation
class ScRideStation;
extern ScRideStation gScRideStation;
class ScRideStation final : public ScBase
{
private:
RideId _rideId = RideId::GetNull();
StationIndex _stationIndex{};
struct RideStationData
{
RideId _rideId = RideId::GetNull();
StationIndex _stationIndex{};
};
public:
ScRideStation(RideId rideId, StationIndex stationIndex);
static void Register(duk_context* ctx);
void Register(JSContext* ctx);
JSValue New(JSContext* ctx, RideId rideId, StationIndex stationIndex);
private:
DukValue start_get() const;
static void Finalize(JSRuntime* rt, JSValue thisVal);
static RideStationData* GetRideStationData(JSValue thisVal);
static RideStation* GetRideStation(JSValue thisVal);
void start_set(const DukValue& value);
int32_t length_get() const;
void length_set(int32_t value);
DukValue entrance_get() const;
void entrance_set(const DukValue& value);
DukValue exit_get() const;
void exit_set(const DukValue& value);
RideStation* GetRideStation() const;
static JSValue start_get(JSContext* ctx, JSValue thisVal);
static JSValue start_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue length_get(JSContext* ctx, JSValue thisVal);
static JSValue length_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue entrance_get(JSContext* ctx, JSValue thisVal);
static JSValue entrance_set(JSContext* ctx, JSValue thisVal, JSValue value);
static JSValue exit_get(JSContext* ctx, JSValue thisVal);
static JSValue exit_set(JSContext* ctx, JSValue thisVal, JSValue value);
};
} // namespace OpenRCT2::Scripting
@@ -25,105 +25,116 @@
using namespace OpenRCT2::Scripting;
using namespace OpenRCT2::TrackMetadata;
std::shared_ptr<ScTrackIterator> ScTrackIterator::FromElement(const CoordsXY& position, int32_t elementIndex)
JSValue ScTrackIterator::FromElement(JSContext* ctx, const CoordsXY& position, int32_t elementIndex)
{
auto el = MapGetNthElementAt(position, elementIndex);
if (el == nullptr)
return nullptr;
return JS_NULL;
auto origin = GetTrackSegmentOrigin(CoordsXYE(position, el));
if (!origin)
return nullptr;
return JS_NULL;
auto trackEl = el->AsTrack();
return std::make_shared<ScTrackIterator>(*origin, trackEl->GetTrackType(), trackEl->GetRideIndex());
return gScTrackIterator.New(ctx, *origin, trackEl->GetTrackType());
}
ScTrackIterator::ScTrackIterator(const CoordsXYZD& position, OpenRCT2::TrackElemType type, RideId ride)
: _position(position)
, _type(type)
, _ride(ride)
void ScTrackIterator::Register(JSContext* ctx)
{
RegisterBaseStr(ctx, "TrackIterator", Finalize);
}
void ScTrackIterator::Register(duk_context* ctx)
JSValue ScTrackIterator::New(JSContext* ctx, const CoordsXYZD& position, OpenRCT2::TrackElemType type)
{
dukglue_register_property(ctx, &ScTrackIterator::position_get, nullptr, "position");
dukglue_register_property(ctx, &ScTrackIterator::segment_get, nullptr, "segment");
dukglue_register_property(ctx, &ScTrackIterator::previousPosition_get, nullptr, "previousPosition");
dukglue_register_property(ctx, &ScTrackIterator::nextPosition_get, nullptr, "nextPosition");
dukglue_register_method(ctx, &ScTrackIterator::previous, "previous");
dukglue_register_method(ctx, &ScTrackIterator::next, "next");
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("position", ScTrackIterator::position_get, nullptr),
JS_CGETSET_DEF("segment", ScTrackIterator::segment_get, nullptr),
JS_CGETSET_DEF("previousPosition", ScTrackIterator::previousPosition_get, nullptr),
JS_CGETSET_DEF("nextPosition", ScTrackIterator::nextPosition_get, nullptr),
JS_CFUNC_DEF("previous", 0, ScTrackIterator::previous),
JS_CFUNC_DEF("next", 0, ScTrackIterator::next),
};
return MakeWithOpaque(ctx, funcs, new TrackIteratorData{ position, type });
}
DukValue ScTrackIterator::position_get() const
void ScTrackIterator::Finalize(JSRuntime* rt, JSValue thisVal)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
return ToDuk(ctx, _position);
TrackIteratorData* data = GetTrackIteratorData(thisVal);
if (data)
delete data;
}
DukValue ScTrackIterator::segment_get() const
ScTrackIterator::TrackIteratorData* ScTrackIterator::GetTrackIteratorData(JSValue thisVal)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
if (_type >= TrackElemType::count)
return ToDuk(ctx, nullptr);
return GetObjectAsDukValue(ctx, std::make_shared<ScTrackSegment>(_type));
return gScTrackIterator.GetOpaque<TrackIteratorData*>(thisVal);
}
DukValue ScTrackIterator::previousPosition_get() const
JSValue ScTrackIterator::position_get(JSContext* ctx, JSValue thisVal)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
auto* data = GetTrackIteratorData(thisVal);
return ToJSValue(ctx, data->_position);
}
auto& ted = GetTrackElementDescriptor(_type);
JSValue ScTrackIterator::segment_get(JSContext* ctx, JSValue thisVal)
{
auto* data = GetTrackIteratorData(thisVal);
if (data->_type >= TrackElemType::count)
return JS_NULL;
return gScTrackSegment.New(ctx, data->_type);
}
JSValue ScTrackIterator::previousPosition_get(JSContext* ctx, JSValue thisVal)
{
auto* data = GetTrackIteratorData(thisVal);
auto& ted = GetTrackElementDescriptor(data->_type);
const auto& seq0 = ted.sequenceData.sequences[0].clearance;
auto pos = _position + CoordsXYZ(seq0.x, seq0.y, seq0.z);
auto pos = data->_position + CoordsXYZ(seq0.x, seq0.y, seq0.z);
auto el = MapGetTrackElementAtOfTypeSeq(pos, _type, 0);
auto el = MapGetTrackElementAtOfTypeSeq(pos, data->_type, 0);
if (el == nullptr)
return ToDuk(ctx, nullptr);
return JS_NULL;
auto posEl = CoordsXYE(pos.x, pos.y, reinterpret_cast<TileElement*>(el));
TrackBeginEnd tbe{};
trackBlockGetPrevious(posEl, &tbe);
CoordsXYZD result(tbe.end_x, tbe.end_y, tbe.begin_z, tbe.begin_direction);
return ToDuk(ctx, result);
return ToJSValue(ctx, result);
}
DukValue ScTrackIterator::nextPosition_get() const
JSValue ScTrackIterator::nextPosition_get(JSContext* ctx, JSValue thisVal)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
auto* data = GetTrackIteratorData(thisVal);
auto& ted = GetTrackElementDescriptor(_type);
auto& ted = GetTrackElementDescriptor(data->_type);
const auto& seq0 = ted.sequenceData.sequences[0].clearance;
auto pos = _position + CoordsXYZ(seq0.x, seq0.y, seq0.z);
auto pos = data->_position + CoordsXYZ(seq0.x, seq0.y, seq0.z);
auto el = MapGetTrackElementAtOfTypeSeq(pos, _type, 0);
auto el = MapGetTrackElementAtOfTypeSeq(pos, data->_type, 0);
if (el == nullptr)
return ToDuk(ctx, nullptr);
return JS_NULL;
auto posEl = CoordsXYE(_position.x, _position.y, reinterpret_cast<TileElement*>(el));
auto posEl = CoordsXYE(data->_position.x, data->_position.y, reinterpret_cast<TileElement*>(el));
CoordsXYE next;
int32_t z{};
int32_t direction{};
trackBlockGetNext(&posEl, &next, &z, &direction);
CoordsXYZD result(next.x, next.y, z, direction);
return ToDuk(ctx, result);
return ToJSValue(ctx, result);
}
bool ScTrackIterator::previous()
JSValue ScTrackIterator::previous(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto& ted = GetTrackElementDescriptor(_type);
const auto& seq0 = ted.sequenceData.sequences[0].clearance;
auto pos = _position + CoordsXYZ(seq0.x, seq0.y, seq0.z);
auto* data = GetTrackIteratorData(thisVal);
auto el = MapGetTrackElementAtOfTypeSeq(pos, _type, 0);
auto& ted = GetTrackElementDescriptor(data->_type);
const auto& seq0 = ted.sequenceData.sequences[0].clearance;
auto pos = data->_position + CoordsXYZ(seq0.x, seq0.y, seq0.z);
auto el = MapGetTrackElementAtOfTypeSeq(pos, data->_type, 0);
if (el == nullptr)
return false;
return JS_NewBool(ctx, false);
auto posEl = CoordsXYE(pos.x, pos.y, reinterpret_cast<TileElement*>(el));
TrackBeginEnd tbe{};
@@ -133,25 +144,27 @@ bool ScTrackIterator::previous()
auto origin = GetTrackSegmentOrigin(prev);
if (origin)
{
_position = *origin;
_type = prev.element->AsTrack()->GetTrackType();
return true;
data->_position = *origin;
data->_type = prev.element->AsTrack()->GetTrackType();
return JS_NewBool(ctx, true);
}
}
return false;
return JS_NewBool(ctx, false);
}
bool ScTrackIterator::next()
JSValue ScTrackIterator::next(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
auto& ted = GetTrackElementDescriptor(_type);
auto* data = GetTrackIteratorData(thisVal);
auto& ted = GetTrackElementDescriptor(data->_type);
const auto& seq0 = ted.sequenceData.sequences[0].clearance;
auto pos = _position + CoordsXYZ(seq0.x, seq0.y, seq0.z);
auto pos = data->_position + CoordsXYZ(seq0.x, seq0.y, seq0.z);
auto el = MapGetTrackElementAtOfTypeSeq(pos, _type, 0);
auto el = MapGetTrackElementAtOfTypeSeq(pos, data->_type, 0);
if (el == nullptr)
return false;
return JS_NewBool(ctx, false);
auto posEl = CoordsXYE(_position.x, _position.y, reinterpret_cast<TileElement*>(el));
auto posEl = CoordsXYE(data->_position.x, data->_position.y, reinterpret_cast<TileElement*>(el));
CoordsXYE next;
int32_t z{};
int32_t direction{};
@@ -160,12 +173,12 @@ bool ScTrackIterator::next()
auto origin = GetTrackSegmentOrigin(next);
if (origin)
{
_position = *origin;
_type = next.element->AsTrack()->GetTrackType();
return true;
data->_position = *origin;
data->_type = next.element->AsTrack()->GetTrackType();
return JS_NewBool(ctx, true);
}
}
return false;
return JS_NewBool(ctx, false);
}
#endif
@@ -12,34 +12,40 @@
#ifdef ENABLE_SCRIPTING
#include "../../../Identifiers.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
#include <cstdint>
#include <memory>
namespace OpenRCT2::Scripting
{
class ScTrackIterator
class ScTrackIterator;
extern ScTrackIterator gScTrackIterator;
class ScTrackIterator final : public ScBase
{
private:
CoordsXYZD _position;
TrackElemType _type;
[[maybe_unused]] RideId _ride;
struct TrackIteratorData
{
CoordsXYZD _position;
TrackElemType _type;
};
public:
static std::shared_ptr<ScTrackIterator> FromElement(const CoordsXY& position, int32_t elementIndex);
static void Register(duk_context* ctx);
ScTrackIterator(const CoordsXYZD& position, TrackElemType type, RideId ride);
static JSValue FromElement(JSContext* ctx, const CoordsXY& position, int32_t elementIndex);
void Register(JSContext* ctx);
JSValue New(JSContext* ctx, const CoordsXYZD& position, TrackElemType type);
private:
DukValue position_get() const;
DukValue segment_get() const;
DukValue previousPosition_get() const;
DukValue nextPosition_get() const;
static void Finalize(JSRuntime* rt, JSValue thisVal);
static TrackIteratorData* GetTrackIteratorData(JSValue thisVal);
bool previous();
bool next();
static JSValue position_get(JSContext* ctx, JSValue thisVal);
static JSValue segment_get(JSContext* ctx, JSValue thisVal);
static JSValue previousPosition_get(JSContext* ctx, JSValue thisVal);
static JSValue nextPosition_get(JSContext* ctx, JSValue thisVal);
static JSValue previous(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue next(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
};
} // namespace OpenRCT2::Scripting
@@ -12,191 +12,231 @@
#include "ScTrackSegment.h"
#include "../../../Context.h"
#include "../../../core/EnumMap.hpp"
#include "../../../ride/TrackData.h"
#include "../../../ride/Vehicle.h"
#include "../../../ride/ted/TrackElementDescriptor.h"
#include "../../ScriptEngine.h"
#include "../../ScriptUtil.hpp"
using namespace OpenRCT2::Scripting;
using namespace OpenRCT2::TrackMetadata;
ScTrackSegment::ScTrackSegment(OpenRCT2::TrackElemType type)
: _type(type)
static JSValue VehicleInfoToJSValue(JSContext* ctx, const VehicleInfo& value)
{
JSValue obj = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, obj, "x", JS_NewInt32(ctx, value.x));
JS_SetPropertyStr(ctx, obj, "y", JS_NewInt32(ctx, value.y));
JS_SetPropertyStr(ctx, obj, "z", JS_NewInt32(ctx, value.z));
JS_SetPropertyStr(ctx, obj, "yaw", JS_NewInt32(ctx, value.yaw));
JS_SetPropertyStr(ctx, obj, "pitch", JS_NewInt32(ctx, EnumValue(value.pitch)));
JS_SetPropertyStr(ctx, obj, "roll", JS_NewInt32(ctx, EnumValue(value.roll)));
return obj;
}
void ScTrackSegment::Register(duk_context* ctx)
void ScTrackSegment::Register(JSContext* ctx)
{
dukglue_register_property(ctx, &ScTrackSegment::type_get, nullptr, "type");
dukglue_register_property(ctx, &ScTrackSegment::description_get, nullptr, "description");
dukglue_register_property(ctx, &ScTrackSegment::elements_get, nullptr, "elements");
dukglue_register_property(ctx, &ScTrackSegment::beginDirection_get, nullptr, "beginDirection");
dukglue_register_property(ctx, &ScTrackSegment::endDirection_get, nullptr, "endDirection");
dukglue_register_property(ctx, &ScTrackSegment::beginSlope_get, nullptr, "beginSlope");
dukglue_register_property(ctx, &ScTrackSegment::endSlope_get, nullptr, "endSlope");
dukglue_register_property(ctx, &ScTrackSegment::beginBank_get, nullptr, "beginBank");
dukglue_register_property(ctx, &ScTrackSegment::endBank_get, nullptr, "endBank");
dukglue_register_property(ctx, &ScTrackSegment::beginZ_get, nullptr, "beginZ");
dukglue_register_property(ctx, &ScTrackSegment::endZ_get, nullptr, "endZ");
dukglue_register_property(ctx, &ScTrackSegment::endX_get, nullptr, "endX");
dukglue_register_property(ctx, &ScTrackSegment::endY_get, nullptr, "endY");
dukglue_register_property(ctx, &ScTrackSegment::length_get, nullptr, "length");
dukglue_register_property(ctx, &ScTrackSegment::nextCurveElement_get, nullptr, "nextSuggestedSegment");
dukglue_register_property(ctx, &ScTrackSegment::previousCurveElement_get, nullptr, "previousSuggestedSegment");
dukglue_register_property(ctx, &ScTrackSegment::getMirrorElement, nullptr, "mirrorSegment");
dukglue_register_property(ctx, &ScTrackSegment::getAlternativeElement, nullptr, "alternateTypeSegment");
dukglue_register_property(ctx, &ScTrackSegment::getPriceModifier, nullptr, "priceModifier");
dukglue_register_property(ctx, &ScTrackSegment::getTrackGroup, nullptr, "trackGroup");
dukglue_register_property(ctx, &ScTrackSegment::getTrackCurvature, nullptr, "turnDirection");
dukglue_register_property(ctx, &ScTrackSegment::getTrackPitchDirection, nullptr, "slopeDirection");
dukglue_register_property(
ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::onlyUnderwater>, nullptr, "onlyAllowedUnderwater");
dukglue_register_property(
ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::onlyAboveGround>, nullptr, "onlyAllowedAboveGround");
dukglue_register_property(ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::allowLiftHill>, nullptr, "allowsChainLift");
dukglue_register_property(ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::banked>, nullptr, "isBanked");
dukglue_register_property(ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::inversionToNormal>, nullptr, "isInversion");
dukglue_register_property(ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::isSteepUp>, nullptr, "isSteepUp");
dukglue_register_property(
ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::startsAtHalfHeight>, nullptr, "startsHalfHeightUp");
dukglue_register_property(ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::isGolfHole>, nullptr, "countsAsInversion");
dukglue_register_property(ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::turnBanked>, nullptr, "isBankedTurn");
dukglue_register_property(ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::turnSloped>, nullptr, "isSlopedTurn");
dukglue_register_property(ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::helix>, nullptr, "isHelix");
dukglue_register_property(
ctx, &ScTrackSegment::getTrackFlag<TrackElementFlag::normalToInversion>, nullptr, "countsAsInversion");
dukglue_register_method(ctx, &ScTrackSegment::getSubpositionLength, "getSubpositionLength");
dukglue_register_method(ctx, &ScTrackSegment::getSubpositions, "getSubpositions");
RegisterBaseStr(ctx, "TrackSegment", Finalize);
}
int32_t ScTrackSegment::type_get() const
JSValue ScTrackSegment::New(JSContext* ctx, OpenRCT2::TrackElemType type)
{
return EnumValue(_type);
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("type", ScTrackSegment::type_get, nullptr),
JS_CGETSET_DEF("description", ScTrackSegment::description_get, nullptr),
JS_CGETSET_DEF("elements", ScTrackSegment::elements_get, nullptr),
JS_CGETSET_DEF("beginDirection", ScTrackSegment::beginDirection_get, nullptr),
JS_CGETSET_DEF("endDirection", ScTrackSegment::endDirection_get, nullptr),
JS_CGETSET_DEF("beginSlope", ScTrackSegment::beginSlope_get, nullptr),
JS_CGETSET_DEF("endSlope", ScTrackSegment::endSlope_get, nullptr),
JS_CGETSET_DEF("beginBank", ScTrackSegment::beginBank_get, nullptr),
JS_CGETSET_DEF("endBank", ScTrackSegment::endBank_get, nullptr),
JS_CGETSET_DEF("beginZ", ScTrackSegment::beginZ_get, nullptr),
JS_CGETSET_DEF("endZ", ScTrackSegment::endZ_get, nullptr),
JS_CGETSET_DEF("endX", ScTrackSegment::endX_get, nullptr),
JS_CGETSET_DEF("endY", ScTrackSegment::endY_get, nullptr),
JS_CGETSET_DEF("length", ScTrackSegment::length_get, nullptr),
JS_CGETSET_DEF("nextSuggestedSegment", ScTrackSegment::nextCurveElement_get, nullptr),
JS_CGETSET_DEF("previousSuggestedSegment", ScTrackSegment::previousCurveElement_get, nullptr),
JS_CGETSET_DEF("mirrorSegment", ScTrackSegment::getMirrorElement, nullptr),
JS_CGETSET_DEF("alternateTypeSegment", ScTrackSegment::getAlternativeElement, nullptr),
JS_CGETSET_DEF("priceModifier", ScTrackSegment::getPriceModifier, nullptr),
JS_CGETSET_DEF("trackGroup", ScTrackSegment::getTrackGroup, nullptr),
JS_CGETSET_DEF("turnDirection", ScTrackSegment::getTrackCurvature, nullptr),
JS_CGETSET_DEF("slopeDirection", ScTrackSegment::getTrackPitchDirection, nullptr),
JS_CGETSET_DEF("onlyAllowedUnderwater", ScTrackSegment::getTrackFlag<TrackElementFlag::onlyUnderwater>, nullptr),
JS_CGETSET_DEF("onlyAllowedAboveGround", ScTrackSegment::getTrackFlag<TrackElementFlag::onlyAboveGround>, nullptr),
JS_CGETSET_DEF("allowsChainLift", ScTrackSegment::getTrackFlag<TrackElementFlag::allowLiftHill>, nullptr),
JS_CGETSET_DEF("isBanked", ScTrackSegment::getTrackFlag<TrackElementFlag::banked>, nullptr),
JS_CGETSET_DEF("isInversion", ScTrackSegment::getTrackFlag<TrackElementFlag::inversionToNormal>, nullptr),
JS_CGETSET_DEF("isSteepUp", ScTrackSegment::getTrackFlag<TrackElementFlag::isSteepUp>, nullptr),
JS_CGETSET_DEF("startsHalfHeightUp", ScTrackSegment::getTrackFlag<TrackElementFlag::startsAtHalfHeight>, nullptr),
JS_CGETSET_DEF("countsAsGolfHole", ScTrackSegment::getTrackFlag<TrackElementFlag::isGolfHole>, nullptr),
JS_CGETSET_DEF("isBankedTurn", ScTrackSegment::getTrackFlag<TrackElementFlag::turnBanked>, nullptr),
JS_CGETSET_DEF("isSlopedTurn", ScTrackSegment::getTrackFlag<TrackElementFlag::turnSloped>, nullptr),
JS_CGETSET_DEF("isHelix", ScTrackSegment::getTrackFlag<TrackElementFlag::helix>, nullptr),
JS_CGETSET_DEF("countsAsInversion", ScTrackSegment::getTrackFlag<TrackElementFlag::normalToInversion>, nullptr),
JS_CFUNC_DEF("getSubpositionLength", 2, ScTrackSegment::getSubpositionLength),
JS_CFUNC_DEF("getSubpositions", 2, ScTrackSegment::getSubpositions),
};
return MakeWithOpaque(ctx, funcs, new TrackSegmentData{ type });
}
std::string ScTrackSegment::description_get() const
void ScTrackSegment::Finalize(JSRuntime* rt, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return LanguageGetString(ted.description);
TrackSegmentData* data = GetTrackSegmentData(thisVal);
if (data)
delete data;
}
int32_t ScTrackSegment::beginZ_get() const
ScTrackSegment::TrackSegmentData* ScTrackSegment::GetTrackSegmentData(JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return ted.coordinates.zBegin;
return gScTrackSegment.GetOpaque<TrackSegmentData*>(thisVal);
}
int32_t ScTrackSegment::beginDirection_get() const
JSValue ScTrackSegment::type_get(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return ted.coordinates.rotationBegin;
const auto* data = GetTrackSegmentData(thisVal);
return JS_NewInt32(ctx, EnumValue(data->_type));
}
int32_t ScTrackSegment::beginSlope_get() const
JSValue ScTrackSegment::description_get(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return EnumValue(ted.definition.pitchStart);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewString(ctx, LanguageGetString(ted.description));
}
int32_t ScTrackSegment::beginBank_get() const
JSValue ScTrackSegment::beginZ_get(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return EnumValue(ted.definition.rollStart);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewInt32(ctx, ted.coordinates.zBegin);
}
int32_t ScTrackSegment::endX_get() const
JSValue ScTrackSegment::beginDirection_get(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return ted.coordinates.x;
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewInt32(ctx, ted.coordinates.rotationBegin);
}
int32_t ScTrackSegment::endY_get() const
JSValue ScTrackSegment::beginSlope_get(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return ted.coordinates.y;
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewInt32(ctx, EnumValue(ted.definition.pitchStart));
}
int32_t ScTrackSegment::endZ_get() const
JSValue ScTrackSegment::beginBank_get(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return ted.coordinates.zEnd;
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewInt32(ctx, EnumValue(ted.definition.rollStart));
}
int32_t ScTrackSegment::endDirection_get() const
JSValue ScTrackSegment::endX_get(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return ted.coordinates.rotationEnd;
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewInt32(ctx, ted.coordinates.x);
}
int32_t ScTrackSegment::endSlope_get() const
JSValue ScTrackSegment::endY_get(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return EnumValue(ted.definition.pitchEnd);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewInt32(ctx, ted.coordinates.y);
}
int32_t ScTrackSegment::endBank_get() const
JSValue ScTrackSegment::endZ_get(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return EnumValue(ted.definition.rollEnd);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewInt32(ctx, ted.coordinates.zEnd);
}
int32_t ScTrackSegment::length_get() const
JSValue ScTrackSegment::endDirection_get(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return ted.pieceLength;
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewInt32(ctx, ted.coordinates.rotationEnd);
}
DukValue ScTrackSegment::elements_get() const
JSValue ScTrackSegment::endSlope_get(JSContext* ctx, JSValue thisVal)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto ctx = scriptEngine.GetContext();
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewUint32(ctx, EnumValue(ted.definition.pitchEnd));
}
const auto& ted = GetTrackElementDescriptor(_type);
JSValue ScTrackSegment::endBank_get(JSContext* ctx, JSValue thisVal)
{
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewUint32(ctx, EnumValue(ted.definition.rollEnd));
}
duk_push_array(ctx);
JSValue ScTrackSegment::length_get(JSContext* ctx, JSValue thisVal)
{
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewUint32(ctx, ted.pieceLength);
}
duk_uarridx_t index = 0;
for (uint8_t i = 0; i < ted.sequenceData.numSequences; i++)
JSValue ScTrackSegment::elements_get(JSContext* ctx, JSValue thisVal)
{
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
JSValue result = JS_NewArray(ctx);
for (int64_t i = 0; i < ted.sequenceData.numSequences; i++)
{
auto& block = ted.sequenceData.sequences[i].clearance;
duk_push_object(ctx);
duk_push_number(ctx, block.x);
duk_put_prop_string(ctx, -2, "x");
duk_push_number(ctx, block.y);
duk_put_prop_string(ctx, -2, "y");
duk_push_number(ctx, block.z);
duk_put_prop_string(ctx, -2, "z");
duk_put_prop_index(ctx, -2, index);
index++;
JSValue element = JS_NewObject(ctx);
JS_SetPropertyStr(ctx, element, "x", JS_NewInt32(ctx, block.x));
JS_SetPropertyStr(ctx, element, "y", JS_NewInt32(ctx, block.y));
JS_SetPropertyStr(ctx, element, "z", JS_NewInt32(ctx, block.z));
JS_SetPropertyInt64(ctx, result, i, element);
}
return DukValue::take_from_stack(ctx);
}
uint16_t ScTrackSegment::getSubpositionLength(uint8_t trackSubposition, uint8_t direction) const
{
return VehicleGetMoveInfoSize(static_cast<VehicleTrackSubposition>(trackSubposition), _type, direction);
}
std::vector<DukValue> ScTrackSegment::getSubpositions(uint8_t trackSubposition, uint8_t direction) const
{
const auto ctx = GetContext()->GetScriptEngine().GetContext();
const uint16_t size = getSubpositionLength(trackSubposition, direction);
const uint16_t typeAndDirection = (EnumValue(_type) << 2) | (direction & 3);
std::vector<DukValue> result;
for (auto idx = 0; idx < size; idx++)
{
result.push_back(ToDuk<VehicleInfo>(ctx, gTrackVehicleInfo[trackSubposition][typeAndDirection]->info[idx]));
}
return result;
}
static DukValue _trackCurveToString(duk_context* ctx, TrackCurve curve)
JSValue ScTrackSegment::getSubpositionLength(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_UINT32(trackSubposition, ctx, argv[0]);
JS_UNPACK_UINT32(direction, ctx, argv[1]);
const auto* data = GetTrackSegmentData(thisVal);
uint16_t length = VehicleGetMoveInfoSize(
static_cast<VehicleTrackSubposition>(trackSubposition), data->_type, static_cast<uint8_t>(direction));
return JS_NewUint32(ctx, length);
}
JSValue ScTrackSegment::getSubpositions(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv)
{
JS_UNPACK_UINT32(trackSubposition, ctx, argv[0]);
JS_UNPACK_UINT32(direction, ctx, argv[1]);
const auto* data = GetTrackSegmentData(thisVal);
const uint16_t size = VehicleGetMoveInfoSize(
static_cast<VehicleTrackSubposition>(trackSubposition), data->_type, static_cast<uint8_t>(direction));
const uint16_t typeAndDirection = (EnumValue(data->_type) << 2) | (direction & 3);
JSValue result = JS_NewArray(ctx);
for (int64_t idx = 0; idx < size; idx++)
{
JSValue subposition = VehicleInfoToJSValue(ctx, gTrackVehicleInfo[trackSubposition][typeAndDirection]->info[idx]);
JS_SetPropertyInt64(ctx, result, idx, subposition);
}
return result;
}
static JSValue _trackCurveToString(JSContext* ctx, TrackCurve curve)
{
static const EnumMap<TrackCurve> map(
{
@@ -211,92 +251,100 @@ static DukValue _trackCurveToString(duk_context* ctx, TrackCurve curve)
{ "right_large", TrackCurve::rightLarge },
});
u8string text = u8string(map[curve]);
return ToDuk<std::string>(ctx, text);
const auto text = std::string(map[curve]);
return JSFromStdString(ctx, text);
}
DukValue ScTrackSegment::nextCurveElement_get() const
JSValue ScTrackSegment::nextCurveElement_get(JSContext* ctx, JSValue thisVal)
{
const auto ctx = GetContext()->GetScriptEngine().GetContext();
const auto& ted = GetTrackElementDescriptor(_type);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
auto nextInChain = ted.curveChain.next;
if (nextInChain.isTrackType)
return ToDuk<int32_t>(ctx, EnumValue(nextInChain.trackType));
return JS_NewInt32(ctx, EnumValue(nextInChain.trackType));
return _trackCurveToString(ctx, nextInChain.curve);
}
DukValue ScTrackSegment::previousCurveElement_get() const
JSValue ScTrackSegment::previousCurveElement_get(JSContext* ctx, JSValue thisVal)
{
const auto ctx = GetContext()->GetScriptEngine().GetContext();
const auto& ted = GetTrackElementDescriptor(_type);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
auto previousInChain = ted.curveChain.previous;
if (previousInChain.isTrackType)
return ToDuk<int32_t>(ctx, EnumValue(previousInChain.trackType));
return JS_NewInt32(ctx, EnumValue(previousInChain.trackType));
return _trackCurveToString(ctx, previousInChain.curve);
}
DukValue ScTrackSegment::getMirrorElement() const
JSValue ScTrackSegment::getMirrorElement(JSContext* ctx, JSValue thisVal)
{
const auto ctx = GetContext()->GetScriptEngine().GetContext();
const auto& ted = GetTrackElementDescriptor(_type);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
if (ted.mirrorElement == TrackElemType::none)
return ToDuk(ctx, nullptr);
return ToDuk<int32_t>(ctx, EnumValue(ted.mirrorElement));
return JS_NULL;
return JS_NewInt32(ctx, EnumValue(ted.mirrorElement));
}
DukValue ScTrackSegment::getAlternativeElement() const
JSValue ScTrackSegment::getAlternativeElement(JSContext* ctx, JSValue thisVal)
{
const auto ctx = GetContext()->GetScriptEngine().GetContext();
const auto& ted = GetTrackElementDescriptor(_type);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
if (ted.alternativeType == TrackElemType::none)
return ToDuk(ctx, nullptr);
return ToDuk<int32_t>(ctx, EnumValue(ted.alternativeType));
return JS_NULL;
return JS_NewInt32(ctx, EnumValue(ted.alternativeType));
}
int32_t ScTrackSegment::getPriceModifier() const
JSValue ScTrackSegment::getPriceModifier(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return ted.priceModifier;
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewInt32(ctx, ted.priceModifier);
}
template<TrackElementFlag flag>
bool ScTrackSegment::getTrackFlag() const
JSValue ScTrackSegment::getTrackFlag(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return ted.flags.has(flag);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewBool(ctx, ted.flags.has(flag));
}
int32_t ScTrackSegment::getTrackGroup() const
JSValue ScTrackSegment::getTrackGroup(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
return EnumValue(ted.definition.group);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
return JS_NewInt32(ctx, EnumValue(ted.definition.group));
}
std::string ScTrackSegment::getTrackCurvature() const
JSValue ScTrackSegment::getTrackCurvature(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
std::string curvature;
if (ted.flags.has(TrackElementFlag::turnLeft))
return "left";
if (ted.flags.has(TrackElementFlag::turnRight))
return "right";
return "straight";
curvature = "left";
else if (ted.flags.has(TrackElementFlag::turnRight))
curvature = "right";
else
curvature = "straight";
return JSFromStdString(ctx, curvature);
}
std::string ScTrackSegment::getTrackPitchDirection() const
JSValue ScTrackSegment::getTrackPitchDirection(JSContext* ctx, JSValue thisVal)
{
const auto& ted = GetTrackElementDescriptor(_type);
const auto* data = GetTrackSegmentData(thisVal);
const auto& ted = GetTrackElementDescriptor(data->_type);
std::string pitch;
if (ted.flags.has(TrackElementFlag::up))
return "up";
if (ted.flags.has(TrackElementFlag::down))
return "down";
return "flat";
pitch = "up";
else if (ted.flags.has(TrackElementFlag::down))
pitch = "down";
else
pitch = "flat";
return JSFromStdString(ctx, pitch);
}
#endif
@@ -11,7 +11,7 @@
#ifdef ENABLE_SCRIPTING
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
#include <cstdint>
#include <string>
@@ -23,56 +23,51 @@ namespace OpenRCT2::TrackMetadata
namespace OpenRCT2::Scripting
{
template<>
inline DukValue ToDuk(duk_context* ctx, const VehicleInfo& value)
{
DukObject dukSubposition(ctx);
dukSubposition.Set("x", value.x);
dukSubposition.Set("y", value.y);
dukSubposition.Set("z", value.z);
dukSubposition.Set("yaw", value.yaw);
dukSubposition.Set("pitch", EnumValue(value.pitch));
dukSubposition.Set("roll", EnumValue(value.roll));
return dukSubposition.Take();
}
class ScTrackSegment;
extern ScTrackSegment gScTrackSegment;
class ScTrackSegment
class ScTrackSegment final : public ScBase
{
private:
TrackElemType _type;
struct TrackSegmentData
{
TrackElemType _type;
};
public:
ScTrackSegment(TrackElemType type);
static void Register(duk_context* ctx);
void Register(JSContext* ctx);
JSValue New(JSContext* ctx, TrackElemType type);
private:
int32_t type_get() const;
std::string description_get() const;
int32_t beginZ_get() const;
int32_t beginDirection_get() const;
int32_t beginSlope_get() const;
int32_t beginBank_get() const;
int32_t endX_get() const;
int32_t endY_get() const;
int32_t endZ_get() const;
int32_t endDirection_get() const;
int32_t endSlope_get() const;
int32_t endBank_get() const;
int32_t length_get() const;
DukValue elements_get() const;
uint16_t getSubpositionLength(uint8_t trackSubposition, uint8_t direction) const;
std::vector<DukValue> getSubpositions(uint8_t trackSubposition, uint8_t direction) const;
DukValue nextCurveElement_get() const;
DukValue previousCurveElement_get() const;
DukValue getMirrorElement() const;
DukValue getAlternativeElement() const;
int32_t getPriceModifier() const;
int32_t getTrackGroup() const;
static void Finalize(JSRuntime* rt, JSValue thisVal);
static TrackSegmentData* GetTrackSegmentData(JSValue thisVal);
static JSValue type_get(JSContext* ctx, JSValue thisVal);
static JSValue description_get(JSContext* ctx, JSValue thisVal);
static JSValue beginZ_get(JSContext* ctx, JSValue thisVal);
static JSValue beginDirection_get(JSContext* ctx, JSValue thisVal);
static JSValue beginSlope_get(JSContext* ctx, JSValue thisVal);
static JSValue beginBank_get(JSContext* ctx, JSValue thisVal);
static JSValue endX_get(JSContext* ctx, JSValue thisVal);
static JSValue endY_get(JSContext* ctx, JSValue thisVal);
static JSValue endZ_get(JSContext* ctx, JSValue thisVal);
static JSValue endDirection_get(JSContext* ctx, JSValue thisVal);
static JSValue endSlope_get(JSContext* ctx, JSValue thisVal);
static JSValue endBank_get(JSContext* ctx, JSValue thisVal);
static JSValue length_get(JSContext* ctx, JSValue thisVal);
static JSValue elements_get(JSContext* ctx, JSValue thisVal);
static JSValue getSubpositionLength(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue getSubpositions(JSContext* ctx, JSValue thisVal, int argc, JSValue* argv);
static JSValue nextCurveElement_get(JSContext* ctx, JSValue thisVal);
static JSValue previousCurveElement_get(JSContext* ctx, JSValue thisVal);
static JSValue getMirrorElement(JSContext* ctx, JSValue thisVal);
static JSValue getAlternativeElement(JSContext* ctx, JSValue thisVal);
static JSValue getPriceModifier(JSContext* ctx, JSValue thisVal);
static JSValue getTrackGroup(JSContext* ctx, JSValue thisVal);
template<TrackMetadata::TrackElementFlag flag>
bool getTrackFlag() const;
std::string getTrackCurvature() const;
std::string getTrackPitchDirection() const;
static JSValue getTrackFlag(JSContext* ctx, JSValue thisVal);
static JSValue getTrackCurvature(JSContext* ctx, JSValue thisVal);
static JSValue getTrackPitchDirection(JSContext* ctx, JSValue thisVal);
};
} // namespace OpenRCT2::Scripting
@@ -16,74 +16,84 @@
#include "../../../localisation/Formatting.h"
#include "../../../management/Award.h"
#include "../../../windows/Intent.h"
#include "../../Duktape.hpp"
namespace OpenRCT2::Scripting
{
ScAward::ScAward(size_t index)
: _index(index)
extern ScAward gScAward;
using OpaqueAwardData = struct
{
size_t index;
};
JSValue ScAward::New(JSContext* ctx, size_t index)
{
return MakeWithOpaque(ctx, funcs, new OpaqueAwardData{ index });
}
void ScAward::Register(duk_context* ctx)
void ScAward::Register(JSContext* ctx)
{
dukglue_register_property(ctx, &ScAward::type_get, nullptr, "type");
dukglue_register_property(ctx, &ScAward::text_get, nullptr, "text");
dukglue_register_property(ctx, &ScAward::positive_get, nullptr, "positive");
dukglue_register_property(ctx, &ScAward::imageId_get, nullptr, "imageId");
dukglue_register_property(ctx, &ScAward::monthsRemaining_get, nullptr, "monthsRemaining");
RegisterBaseStr(ctx, "Award", Finalize);
}
Award* ScAward::GetAward() const
void ScAward::Finalize(JSRuntime* rt, JSValue thisVal)
{
return &getGameState().park.currentAwards[_index];
OpaqueAwardData* data = gScAward.GetOpaque<OpaqueAwardData*>(thisVal);
if (data)
delete data;
}
std::string ScAward::type_get() const
Award* ScAward::GetAward(JSValue thisVal)
{
auto award = GetAward();
OpaqueAwardData* data = gScAward.GetOpaque<OpaqueAwardData*>(thisVal);
return &getGameState().park.currentAwards[data->index];
}
JSValue ScAward::type_get(JSContext* ctx, JSValue thisVal)
{
auto award = GetAward(thisVal);
if (award == nullptr)
return {};
return JSFromStdString(ctx, {});
return AwardTypeToString(award->Type).value_or(std::string());
return JSFromStdString(ctx, AwardTypeToString(award->Type).value_or(std::string()));
}
std::string ScAward::text_get() const
JSValue ScAward::text_get(JSContext* ctx, JSValue thisVal)
{
auto award = GetAward();
auto award = GetAward(thisVal);
if (award == nullptr)
return {};
return JSFromStdString(ctx, {});
Formatter ft{};
ft.Add<StringId>(AwardGetText(award->Type));
return FormatStringIDLegacy(STR_STRINGID, ft.Data());
return JSFromStdString(ctx, FormatStringIDLegacy(STR_STRINGID, ft.Data()));
}
uint16_t ScAward::monthsRemaining_get() const
JSValue ScAward::monthsRemaining_get(JSContext* ctx, JSValue thisVal)
{
auto award = GetAward();
auto award = GetAward(thisVal);
if (award == nullptr)
return {};
return JS_NewInt32(ctx, {});
return award->Time;
return JS_NewInt32(ctx, award->Time);
}
bool ScAward::positive_get() const
JSValue ScAward::positive_get(JSContext* ctx, JSValue thisVal)
{
auto award = GetAward();
auto award = GetAward(thisVal);
if (award == nullptr)
return {};
return JS_NewBool(ctx, {});
return AwardIsPositive(award->Type);
return JS_NewBool(ctx, AwardIsPositive(award->Type));
}
uint32_t ScAward::imageId_get() const
JSValue ScAward::imageId_get(JSContext* ctx, JSValue thisVal)
{
auto award = GetAward();
auto award = GetAward(thisVal);
if (award == nullptr)
return {};
return JS_NewUint32(ctx, {});
return AwardGetSprite(award->Type);
return JS_NewUint32(ctx, AwardGetSprite(award->Type));
}
} // namespace OpenRCT2::Scripting
@@ -13,9 +13,9 @@
#include "../../../Context.h"
#include "../../../management/Award.h"
#include "../../Duktape.hpp"
#include "../../ScriptEngine.h"
#include <quickjs.h>
#include <string>
namespace OpenRCT2::Scripting
@@ -60,24 +60,31 @@ namespace OpenRCT2::Scripting
return std::nullopt;
}
class ScAward
class ScAward;
extern ScAward gScAward;
class ScAward final : public ScBase
{
private:
size_t _index{};
public:
ScAward(size_t index);
static void Register(duk_context* ctx);
JSValue New(JSContext* ctx, size_t index);
void Register(JSContext* ctx);
private:
Award* GetAward() const;
static void Finalize(JSRuntime* rt, JSValue thisVal);
static Award* GetAward(JSValue thisVal);
std::string type_get() const;
std::string text_get() const;
uint16_t monthsRemaining_get() const;
bool positive_get() const;
uint32_t imageId_get() const;
static JSValue type_get(JSContext* ctx, JSValue thisVal);
static JSValue text_get(JSContext* ctx, JSValue thisVal);
static JSValue monthsRemaining_get(JSContext* ctx, JSValue thisVal);
static JSValue positive_get(JSContext* ctx, JSValue thisVal);
static JSValue imageId_get(JSContext* ctx, JSValue thisVal);
static constexpr JSCFunctionListEntry funcs[] = {
JS_CGETSET_DEF("type", &ScAward::type_get, nullptr), JS_CGETSET_DEF("text", &ScAward::text_get, nullptr),
JS_CGETSET_DEF("positive", &ScAward::positive_get, nullptr),
JS_CGETSET_DEF("imageId", &ScAward::imageId_get, nullptr),
JS_CGETSET_DEF("monthsRemaining", &ScAward::monthsRemaining_get, nullptr)
};
};
} // namespace OpenRCT2::Scripting

Some files were not shown because too many files have changed in this diff Show More