1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-23 06:44:38 +01:00

Merge pull request #16872 from IntelOrca/plugin/image-apis

[Plugin] Add support for custom images
This commit is contained in:
Michael Steenbeek
2022-03-28 20:27:10 +02:00
committed by GitHub
12 changed files with 744 additions and 31 deletions

View File

@@ -22,6 +22,7 @@
- Feature: [#16800] [Plugin] Add lift hill speed properties to API.
- Feature: [#16806] Parkobj can load sprites from RCT image archives.
- Feature: [#16831] Allow ternary colours for small and large scenery objects.
- Feature: [#16872] [Plugin] Add support for custom images.
- Improved: [#3517] Cheats are now saved with the park.
- Improved: [#10150] Ride stations are now properly checked if theyre sheltered.
- Improved: [#10664, #16072] Visibility status can be modified directly in the Tile Inspector's list.

View File

@@ -2794,6 +2794,125 @@ declare global {
* Useful for displaying how fragmented the allocated image list is.
*/
getAvailableAllocationRanges(): ImageIndexRange[];
/**
* Allocates one or more contigous image IDs.
* @param count The number of image IDs to allocate.
* @returns the range of allocated image IDs or null if the range could not be allocated.
*/
allocate(count: number): ImageIndexRange | null;
/**
* Frees one or more contigous image IDs.
* An error will occur if attempting the given range contains an ID not owned by the plugin.
* @param range The range of images to free.
*/
free(range: ImageIndexRange): void;
/**
* Gets the metadata for a given image.
*/
getImageInfo(id: number): ImageInfo | undefined;
/**
* Gets the pixel data for a given image ID.
*/
getPixelData(id: number): PixelData | undefined;
/**
* Sets the pixel data for a given image ID.
*
* Will error if given an ID of an image not owned by this plugin.
* @param id The id of the image to set the pixels of.
* @param data The pixel data.
*/
setPixelData(id: number, data: PixelData): void;
/**
* Calls the given function with a {@link GraphicsContext} for the given image, allowing the
* ability to draw directly to it.
*
* Allocates or reallocates the image if not previously allocated or if the size is changed.
* The pixels of the image will persist between calls, so you can draw over the top of what
* is currently there. The default pixel colour will be 0 (transparent).
*
* Drawing a large number of pixels each frame can be expensive, so caching as many as you
* can in images is a good way to improve performance.
*
* Will error if given an ID of an image not owned by this plugin.
* @param id The id of the image to draw to.
* @param size The size the image that should be allocated.
* @param callback The function that will draw to the image.
*/
draw(id: number, size: ScreenSize, callback: (g: GraphicsContext) => void): void;
}
type PixelData = RawPixelData | RlePixelData | PngPixelData;
/**
* Raw pixel data that is not encoded. A contiguous sequence of bytes
* representing the 8bpp pixel values with a optional padding between
* each horizontal row.
*/
interface RawPixelData {
type: 'raw';
width: number;
height: number;
/**
* The length of each horizontal row in bytes.
*/
stride?: number;
/**
* Data can either by a:
* - A base64 string.
* - An array of bytes
* - A {@link Uint8Array} of bytes
*/
data: string | number | Uint8Array;
}
/**
* Pixel data that is encoded as RCT run-length encoded data.
*/
interface RlePixelData {
type: 'rle';
width: number;
height: number;
/**
* Data can either by a:
* - A base64 string.
* - An array of bytes
* - A {@link Uint8Array} of bytes
*/
data: string | number | Uint8Array;
}
/**
* Pixel data that is encoded as a .png file.
*/
interface PngPixelData {
type: 'png';
/**
* How the colours of the .png file are converted to the OpenRCT2 palette.
* If keep is specified for palette, the raw 8bpp .png bytes will be loaded. The palette
* in the .png will not be read. This will improve load performance.
* Closest will find the closest matching colour from the OpenRCT2 palette.
* Dither will add noise to reduce colour banding for images rich in colour.
* If undefined, only colours that are in OpenRCT2 palette will be imported.
*/
palette?: 'keep' | 'closest' | 'dither';
/**
* Data can either by a:
* - A base64 string.
* - An array of bytes
* - A {@link Uint8Array} of bytes
*/
data: string | number | Uint8Array;
}
interface ImageIndexRange {

View File

@@ -51,6 +51,7 @@
<ClInclude Include="interface\Viewport.h" />
<ClInclude Include="interface\Widget.h" />
<ClInclude Include="interface\Window.h" />
<ClInclude Include="scripting\CustomImages.h" />
<ClInclude Include="scripting\CustomListView.h" />
<ClInclude Include="scripting\CustomMenu.h" />
<ClInclude Include="scripting\CustomWindow.h" />
@@ -105,6 +106,7 @@
<ClCompile Include="interface\ViewportInteraction.cpp" />
<ClCompile Include="interface\Widget.cpp" />
<ClCompile Include="interface\Window.cpp" />
<ClCompile Include="scripting\CustomImages.cpp" />
<ClCompile Include="scripting\CustomListView.cpp" />
<ClCompile Include="scripting\CustomMenu.cpp" />
<ClCompile Include="scripting\CustomWindow.cpp" />

View File

@@ -0,0 +1,471 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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 "CustomImages.h"
# include "ScGraphicsContext.hpp"
# include <openrct2/Context.h>
# include <openrct2/drawing/Image.h>
# include <openrct2/drawing/ImageImporter.h>
# include <openrct2/drawing/X8DrawingEngine.h>
# include <openrct2/scripting/Plugin.h>
using namespace OpenRCT2::Drawing;
namespace OpenRCT2::Scripting
{
enum class PixelDataKind
{
Unknown,
Raw,
Rle,
Palette,
Png
};
enum class PixelDataPaletteKind
{
None,
Keep,
Closest,
Dither
};
struct PixelData
{
PixelDataKind Type;
int32_t Width;
int32_t Height;
int32_t Stride;
PixelDataPaletteKind Palette;
DukValue Data;
};
struct AllocatedImageList
{
std::shared_ptr<Plugin> Owner;
ImageList Range;
};
static std::vector<AllocatedImageList> _allocatedImages;
static void FreeImages(ImageList range)
{
for (ImageIndex i = 0; i < range.Count; i++)
{
auto index = range.BaseId + i;
auto g1 = gfx_get_g1_element(index);
if (g1 != nullptr)
{
// Free pixel data
delete[] g1->offset;
// Replace slot with empty element
rct_g1_element empty{};
gfx_set_g1_element(index, &empty);
}
}
gfx_object_free_images(range.BaseId, range.Count);
}
std::optional<ImageList> AllocateCustomImages(const std::shared_ptr<Plugin>& plugin, uint32_t count)
{
std::vector<rct_g1_element> images;
images.resize(count);
auto base = gfx_object_allocate_images(images.data(), count);
if (base == ImageIndexUndefined)
{
return {};
}
auto range = ImageList(base, count);
AllocatedImageList item;
item.Owner = plugin;
item.Range = range;
_allocatedImages.push_back(std::move(item));
return range;
}
bool FreeCustomImages(const std::shared_ptr<Plugin>& plugin, ImageList range)
{
auto it = std::find_if(
_allocatedImages.begin(), _allocatedImages.end(),
[&plugin, range](const AllocatedImageList& item) { return item.Owner == plugin && item.Range == range; });
if (it == _allocatedImages.end())
{
return false;
}
FreeImages(it->Range);
_allocatedImages.erase(it);
return true;
}
bool DoesPluginOwnImage(const std::shared_ptr<Plugin>& plugin, ImageIndex index)
{
auto it = std::find_if(
_allocatedImages.begin(), _allocatedImages.end(),
[&plugin, index](const AllocatedImageList& item) { return item.Owner == plugin && item.Range.Contains(index); });
return it != _allocatedImages.end();
}
static void FreeCustomImages(const std::shared_ptr<Plugin>& plugin)
{
auto it = _allocatedImages.begin();
while (it != _allocatedImages.end())
{
if (it->Owner == plugin)
{
FreeImages(it->Range);
it = _allocatedImages.erase(it);
}
else
{
it++;
}
}
}
void InitialiseCustomImages(ScriptEngine& scriptEngine)
{
scriptEngine.SubscribeToPluginStoppedEvent([](std::shared_ptr<Plugin> plugin) -> void { FreeCustomImages(plugin); });
}
DukValue DukGetImageInfo(duk_context* ctx, ImageIndex id)
{
auto* g1 = gfx_get_g1_element(id);
if (g1 == nullptr)
{
return ToDuk(ctx, undefined);
}
DukObject obj(ctx);
obj.Set("id", id);
obj.Set("offset", ToDuk<ScreenCoordsXY>(ctx, { g1->x_offset, g1->y_offset }));
obj.Set("width", g1->width);
obj.Set("height", g1->height);
obj.Set("isBMP", (g1->flags & G1_FLAG_BMP) != 0);
obj.Set("isRLE", (g1->flags & G1_FLAG_RLE_COMPRESSION) != 0);
obj.Set("isPalette", (g1->flags & G1_FLAG_PALETTE) != 0);
obj.Set("noZoom", (g1->flags & G1_FLAG_NO_ZOOM_DRAW) != 0);
if (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE)
{
obj.Set("nextZoomId", id - g1->zoomed_offset);
}
else
{
obj.Set("nextZoomId", undefined);
}
return obj.Take();
}
static const char* GetPixelDataTypeForG1(const rct_g1_element& g1)
{
if (g1.flags & G1_FLAG_RLE_COMPRESSION)
return "rle";
else if (g1.flags & G1_FLAG_PALETTE)
return "palette";
return "raw";
}
DukValue DukGetImagePixelData(duk_context* ctx, ImageIndex id)
{
auto* g1 = gfx_get_g1_element(id);
if (g1 == nullptr)
{
return ToDuk(ctx, undefined);
}
auto dataSize = g1_calculate_data_size(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);
DukObject obj(ctx);
obj.Set("type", type);
obj.Set("width", g1->width);
obj.Set("height", g1->height);
obj.Set("data", data);
return obj.Take();
}
static std::vector<uint8_t> GetBufferFromDukStack(duk_context* ctx)
{
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())
{
// 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++)
{
if (duk_get_prop_index(ctx, -1, i))
{
result[i] = duk_get_int(ctx, -1) & 0xFF;
duk_pop(ctx);
}
}
duk_pop(ctx);
}
else if (data.type() == DukValue::Type::STRING)
{
// From base64 string
data.push();
duk_base64_decode(ctx, -1);
result = GetBufferFromDukStack(ctx);
duk_pop(ctx);
}
else if (data.type() == DukValue::Type::OBJECT)
{
// From Uint8Array
data.push();
result = GetBufferFromDukStack(ctx);
duk_pop(ctx);
}
return result;
}
static std::vector<uint8_t> RemovePadding(const std::vector<uint8_t>& srcData, const PixelData& pixelData)
{
std::vector<uint8_t> unpadded(pixelData.Width * pixelData.Height, 0);
auto* src = srcData.data();
auto* dst = unpadded.data();
for (int32_t y = 0; y < pixelData.Height; y++)
{
std::memcpy(dst, src, pixelData.Width);
src += pixelData.Stride;
dst += pixelData.Width;
}
return unpadded;
}
static std::vector<uint8_t> GetBufferFromPixelData(duk_context* ctx, PixelData& pixelData)
{
std::vector<uint8_t> imageData;
switch (pixelData.Type)
{
case PixelDataKind::Raw:
{
auto data = DukGetDataFromBufferLikeObject(pixelData.Data);
if (pixelData.Stride != pixelData.Width)
{
// Make sure data is expected size for RemovePadding
data.resize(pixelData.Stride * pixelData.Height);
data = RemovePadding(data, pixelData);
}
// Make sure data is expected size
data.resize(pixelData.Width * pixelData.Height);
break;
}
case PixelDataKind::Rle:
{
imageData = DukGetDataFromBufferLikeObject(pixelData.Data);
break;
}
case PixelDataKind::Png:
{
auto imageFormat = pixelData.Palette == PixelDataPaletteKind::Keep ? IMAGE_FORMAT::PNG : IMAGE_FORMAT::PNG_32;
auto palette = pixelData.Palette == PixelDataPaletteKind::Keep ? ImageImporter::Palette::KeepIndices
: ImageImporter::Palette::OpenRCT2;
auto importMode = ImageImporter::ImportMode::Default;
if (pixelData.Palette == PixelDataPaletteKind::Closest)
importMode = ImageImporter::ImportMode::Closest;
else if (pixelData.Palette == PixelDataPaletteKind::Dither)
importMode = ImageImporter::ImportMode::Dithering;
auto pngData = DukGetDataFromBufferLikeObject(pixelData.Data);
auto image = Imaging::ReadFromBuffer(pngData, imageFormat);
ImageImporter importer;
auto importResult = importer.Import(image, 0, 0, palette, ImageImporter::ImportFlags::RLE, importMode);
pixelData.Type = PixelDataKind::Rle;
pixelData.Width = importResult.Element.width;
pixelData.Height = importResult.Element.height;
imageData = std::move(importResult.Buffer);
break;
}
default:
throw std::runtime_error("Unsupported pixel data type.");
}
return imageData;
}
template<> PixelDataKind FromDuk(const DukValue& d)
{
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;
}
return PixelDataKind::Unknown;
}
template<> PixelDataPaletteKind FromDuk(const DukValue& d)
{
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;
}
return PixelDataPaletteKind::None;
}
static PixelData GetPixelDataFromDuk(const DukValue& dukPixelData)
{
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"];
return pixelData;
}
static void ReplacePixelDataForImage(ImageIndex id, const PixelData& pixelData, std::vector<uint8_t>&& data)
{
// Setup the g1 element
rct_g1_element el{};
auto* lastel = gfx_get_g1_element(id);
if (lastel != nullptr)
{
el = *lastel;
delete[] el.offset;
}
// Copy data into new unmanaged uint8_t[]
auto newData = new uint8_t[data.size()];
std::memcpy(newData, data.data(), data.size());
el.offset = newData;
el.width = pixelData.Width;
el.height = pixelData.Height;
el.flags = 0;
if (pixelData.Type == PixelDataKind::Rle)
{
el.flags |= G1_FLAG_RLE_COMPRESSION;
}
gfx_set_g1_element(id, &el);
drawing_engine_invalidate_image(id);
}
void DukSetPixelData(duk_context* ctx, ImageIndex id, const DukValue& dukPixelData)
{
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());
}
}
void DukDrawCustomImage(ScriptEngine& scriptEngine, ImageIndex id, ScreenSize size, const DukValue& callback)
{
auto* ctx = scriptEngine.GetContext();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
auto drawingEngine = std::make_unique<X8DrawingEngine>(GetContext()->GetUiContext());
rct_drawpixelinfo dpi;
dpi.DrawingEngine = drawingEngine.get();
dpi.width = size.width;
dpi.height = size.height;
auto createNewImage = false;
auto g1 = gfx_get_g1_element(id);
if (g1 == nullptr || g1->width != size.width || g1->height != size.height || (g1->flags & G1_FLAG_RLE_COMPRESSION))
{
createNewImage = true;
}
if (createNewImage)
{
auto bufferSize = size.width * size.height;
dpi.bits = new uint8_t[bufferSize];
std::memset(dpi.bits, 0, bufferSize);
// Draw the original image if we are creating a new one
gfx_draw_sprite(&dpi, ImageId(id), { 0, 0 });
}
else
{
dpi.bits = g1->offset;
}
auto dukG = GetObjectAsDukValue(ctx, std::make_shared<ScGraphicsContext>(ctx, dpi));
scriptEngine.ExecutePluginCall(plugin, callback, { dukG }, false);
if (createNewImage)
{
rct_g1_element newg1{};
if (g1 != nullptr)
{
delete[] g1->offset;
newg1 = *g1;
}
newg1.offset = dpi.bits;
newg1.width = size.width;
newg1.height = size.height;
newg1.flags = 0;
gfx_set_g1_element(id, &newg1);
}
drawing_engine_invalidate_image(id);
}
} // namespace OpenRCT2::Scripting
#endif

View File

@@ -0,0 +1,32 @@
/*****************************************************************************
* Copyright (c) 2014-2022 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/drawing/Image.h>
# include <openrct2/drawing/ImageId.hpp>
# include <openrct2/scripting/Duktape.hpp>
# include <openrct2/scripting/ScriptEngine.h>
namespace OpenRCT2::Scripting
{
void InitialiseCustomImages(ScriptEngine& scriptEngine);
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);
} // namespace OpenRCT2::Scripting
#endif

View File

@@ -11,6 +11,8 @@
#ifdef ENABLE_SCRIPTING
# include "CustomImages.h"
# include <openrct2/drawing/Drawing.h>
# include <openrct2/scripting/Duktape.hpp>
@@ -149,32 +151,7 @@ namespace OpenRCT2::Scripting
DukValue getImage(uint32_t id)
{
auto* g1 = gfx_get_g1_element(id);
if (g1 == nullptr)
{
return ToDuk(_ctx, undefined);
}
DukObject obj(_ctx);
obj.Set("id", id);
obj.Set("offset", ToDuk<ScreenCoordsXY>(_ctx, { g1->x_offset, g1->y_offset }));
obj.Set("width", g1->width);
obj.Set("height", g1->height);
obj.Set("isBMP", (g1->flags & G1_FLAG_BMP) != 0);
obj.Set("isRLE", (g1->flags & G1_FLAG_RLE_COMPRESSION) != 0);
obj.Set("isPalette", (g1->flags & G1_FLAG_PALETTE) != 0);
obj.Set("noZoom", (g1->flags & G1_FLAG_NO_ZOOM_DRAW) != 0);
if (g1->flags & G1_FLAG_HAS_ZOOM_SPRITE)
{
obj.Set("nextZoomId", id - g1->zoomed_offset);
}
else
{
obj.Set("nextZoomId", undefined);
}
return obj.Take();
return DukGetImageInfo(_ctx, id);
}
DukValue measureText(const std::string& text)

View File

@@ -11,6 +11,9 @@
#ifdef ENABLE_SCRIPTING
# include "CustomImages.h"
# include <openrct2/Context.h>
# include <openrct2/drawing/Image.h>
# include <openrct2/scripting/Duktape.hpp>
# include <openrct2/sprites.h>
@@ -32,6 +35,12 @@ namespace OpenRCT2::Scripting
{
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");
}
private:
@@ -74,6 +83,66 @@ namespace OpenRCT2::Scripting
return DukValue::take_from_stack(_ctx);
}
DukValue allocate(int32_t count)
{
auto& scriptEngine = GetContext()->GetScriptEngine();
auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin();
auto range = AllocateCustomImages(plugin, count);
return range ? CreateImageIndexRange(range->BaseId, range->Count) : ToDuk(_ctx, undefined);
}
void free(const DukValue& dukRange)
{
auto start = dukRange["start"].as_int();
auto count = dukRange["count"].as_int();
ImageList range(start, count);
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.");
}
DukSetPixelData(_ctx, id, pixelData);
}
void draw(int32_t id, const DukValue& dukSize, const DukValue& callback)
{
auto width = dukSize["width"].as_int();
auto height = dukSize["height"].as_int();
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.");
}
DukDrawCustomImage(scriptEngine, id, { width, height }, callback);
}
DukValue CreateImageIndexRange(size_t start, size_t count) const
{
DukObject obj(_ctx);

View File

@@ -11,6 +11,7 @@
# include "UiExtensions.h"
# include "CustomImages.h"
# include "CustomMenu.h"
# include "ScGraphicsContext.hpp"
# include "ScImageManager.hpp"
@@ -55,6 +56,7 @@ void UiScriptExtensions::Extend(ScriptEngine& scriptEngine)
ScTitleSequencePark::Register(ctx);
ScWindow::Register(ctx);
InitialiseCustomImages(scriptEngine);
InitialiseCustomMenuItems(scriptEngine);
scriptEngine.SubscribeToPluginStoppedEvent(
[](std::shared_ptr<Plugin> plugin) -> void { CloseWindowsOwnedByPlugin(plugin); });

View File

@@ -9,6 +9,8 @@
#pragma once
#include "ImageId.hpp"
#include <cstddef>
#include <cstdint>
#include <list>
@@ -17,10 +19,42 @@ struct rct_g1_element;
struct ImageList
{
uint32_t BaseId;
uint32_t Count;
ImageIndex BaseId{};
ImageIndex Count{};
ImageList() = default;
ImageList(ImageIndex baseId, ImageIndex count)
: BaseId(baseId)
, Count(count)
{
}
bool Contains(ImageIndex index) const
{
return index >= BaseId && index < GetEnd();
}
ImageIndex GetEnd() const
{
return BaseId + Count;
}
static ImageList FromBeginEnd(ImageIndex begin, ImageIndex end)
{
return ImageList(begin, end - begin);
}
};
constexpr bool operator==(const ImageList& lhs, const ImageList& rhs)
{
return lhs.BaseId == rhs.BaseId && lhs.Count == rhs.Count;
}
constexpr bool operator!=(const ImageList& lhs, const ImageList& rhs)
{
return !(lhs == rhs);
}
uint32_t gfx_object_allocate_images(const rct_g1_element* images, uint32_t count);
void gfx_object_free_images(uint32_t baseImageId, uint32_t count);
void gfx_object_check_all_images_freed();

View File

@@ -89,17 +89,18 @@ void Plugin::Start()
throw std::runtime_error("No main function specified.");
}
_hasStarted = true;
mainFunc.push();
auto result = duk_pcall(_context, 0);
if (result != DUK_ERR_NONE)
{
auto val = std::string(duk_safe_to_string(_context, -1));
duk_pop(_context);
_hasStarted = false;
throw std::runtime_error("[" + _metadata.Name + "] " + val);
}
duk_pop(_context);
_hasStarted = true;
}
void Plugin::StopBegin()

View File

@@ -590,6 +590,11 @@ void ScriptEngine::StopUnloadRegisterAllPlugins()
void ScriptEngine::LoadTransientPlugins()
{
if (!_initialised)
{
Initialise();
RefreshPlugins();
}
_transientPluginsEnabled = true;
}

View File

@@ -46,7 +46,7 @@ namespace OpenRCT2
namespace OpenRCT2::Scripting
{
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 51;
static constexpr int32_t OPENRCT2_PLUGIN_API_VERSION = 52;
// Versions marking breaking changes.
static constexpr int32_t API_VERSION_33_PEEP_DEPRECATION = 33;