diff --git a/src/openrct2-ui/scripting/CustomImages.cpp b/src/openrct2-ui/scripting/CustomImages.cpp index 902f828128..5204148806 100644 --- a/src/openrct2-ui/scripting/CustomImages.cpp +++ b/src/openrct2-ui/scripting/CustomImages.cpp @@ -11,7 +11,11 @@ # include "CustomImages.h" +# include +# include # include +# include + using namespace OpenRCT2::Drawing; namespace OpenRCT2::Scripting @@ -43,6 +47,97 @@ namespace OpenRCT2::Scripting DukValue Data; }; + struct AllocatedImageList + { + std::shared_ptr Owner; + ImageList Range; + }; + + static std::vector _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 AllocateCustomImages(const std::shared_ptr& plugin, uint32_t count) + { + std::vector 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, 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, 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) + { + 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) -> void { FreeCustomImages(plugin); }); + } + DukValue DukGetImageInfo(duk_context* ctx, ImageIndex id) { auto* g1 = gfx_get_g1_element(id); diff --git a/src/openrct2-ui/scripting/CustomImages.h b/src/openrct2-ui/scripting/CustomImages.h index ff9e5af237..98e954833f 100644 --- a/src/openrct2-ui/scripting/CustomImages.h +++ b/src/openrct2-ui/scripting/CustomImages.h @@ -11,11 +11,17 @@ #ifdef ENABLE_SCRIPTING +# include # include # include +# include namespace OpenRCT2::Scripting { + void InitialiseCustomImages(ScriptEngine& scriptEngine); + std::optional AllocateCustomImages(const std::shared_ptr& plugin, uint32_t count); + bool FreeCustomImages(const std::shared_ptr& plugin, ImageList range); + bool DoesPluginOwnImage(const std::shared_ptr& 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); diff --git a/src/openrct2-ui/scripting/ScImageManager.hpp b/src/openrct2-ui/scripting/ScImageManager.hpp index 923f173fb6..ced6dd20d1 100644 --- a/src/openrct2-ui/scripting/ScImageManager.hpp +++ b/src/openrct2-ui/scripting/ScImageManager.hpp @@ -13,6 +13,7 @@ # include "CustomImages.h" +# include # include # include # include @@ -83,22 +84,25 @@ namespace OpenRCT2::Scripting DukValue allocate(int32_t count) { - std::vector images; - images.resize(count); - - auto base = gfx_object_allocate_images(images.data(), count); - if (base == ImageIndexUndefined) - { - return ToDuk(_ctx, nullptr); - } - return CreateImageIndexRange(base, count); + auto& scriptEngine = GetContext()->GetScriptEngine(); + auto plugin = scriptEngine.GetExecInfo().GetCurrentPlugin(); + auto range = AllocateCustomImages(plugin, count); + return range ? CreateImageIndexRange(range->BaseId, range->Count) : ToDuk(_ctx, nullptr); } - void free(const DukValue& range) + void free(const DukValue& dukRange) { - auto start = range["start"].as_int(); - auto count = range["count"].as_int(); - gfx_object_free_images(start, count); + 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) @@ -113,6 +117,13 @@ namespace OpenRCT2::Scripting 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); } diff --git a/src/openrct2-ui/scripting/UiExtensions.cpp b/src/openrct2-ui/scripting/UiExtensions.cpp index 9d43a6430d..3e74f27fe5 100644 --- a/src/openrct2-ui/scripting/UiExtensions.cpp +++ b/src/openrct2-ui/scripting/UiExtensions.cpp @@ -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) -> void { CloseWindowsOwnedByPlugin(plugin); }); diff --git a/src/openrct2/drawing/Image.h b/src/openrct2/drawing/Image.h index 359caaa271..114897afc5 100644 --- a/src/openrct2/drawing/Image.h +++ b/src/openrct2/drawing/Image.h @@ -12,15 +12,48 @@ #include #include #include +#include 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();